17b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala/* 27b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala * Copyright (C) 2018 The Android Open Source Project 37b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala * 47b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala * Licensed under the Apache License, Version 2.0 (the "License"); 57b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala * you may not use this file except in compliance with the License. 67b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala * You may obtain a copy of the License at 77b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala * 87b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala * http://www.apache.org/licenses/LICENSE-2.0 97b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala * 107b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala * Unless required by applicable law or agreed to in writing, software 117b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala * distributed under the License is distributed on an "AS IS" BASIS, 127b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 137b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala * See the License for the specific language governing permissions and 147b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala * limitations under the License. 157b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala */ 167b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala 177b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala#ifndef ANDROID_SERVERS_DISTORTIONMAPPER_H 187b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala#define ANDROID_SERVERS_DISTORTIONMAPPER_H 197b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala 207b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala#include <utils/Errors.h> 217b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala#include <array> 227b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala#include <mutex> 237b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala 247b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala#include "camera/CameraMetadata.h" 257b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala 267b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvalanamespace android { 277b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala 287b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvalanamespace camera3 { 297b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala 307b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala/** 317b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala * Utilities to transform between raw (distorted) and warped (corrected) coordinate systems 327b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala * for cameras that support geometric distortion 337b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala */ 347b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvalaclass DistortionMapper { 357b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala public: 367b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala DistortionMapper(); 377b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala 387b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala /** 397b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala * Check whether distortion correction is supported by the camera HAL 407b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala */ 417b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala static bool isDistortionSupported(const CameraMetadata &deviceInfo); 427b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala 437b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala /** 447b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala * Update static lens calibration info from camera characteristics 457b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala */ 467b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala status_t setupStaticInfo(const CameraMetadata &deviceInfo); 477b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala 487b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala /** 497b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala * Return whether distortion correction can be applied currently 507b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala */ 517b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala bool calibrationValid() const; 527b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala 537b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala /** 547b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala * Correct capture request if distortion correction is enabled 557b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala */ 567b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala status_t correctCaptureRequest(CameraMetadata *request); 577b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala 587b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala /** 597b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala * Correct capture result if distortion correction is enabled 607b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala */ 617b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala status_t correctCaptureResult(CameraMetadata *request); 627b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala 637b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala 647b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala public: // Visible for testing. Not guarded by mutex; do not use concurrently 657b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala /** 667b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala * Update lens calibration from capture results or equivalent 677b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala */ 687b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala status_t updateCalibration(const CameraMetadata &result); 697b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala 707b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala /** 717b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala * Transform from distorted (original) to corrected (warped) coordinates. 727b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala * Coordinates are transformed in-place 737b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala * 747b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala * coordPairs: A pointer to an array of consecutive (x,y) points 757b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala * coordCount: Number of (x,y) pairs to transform 767b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala */ 777b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala status_t mapRawToCorrected(int32_t *coordPairs, int coordCount); 787b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala 797b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala /** 807b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala * Transform from distorted (original) to corrected (warped) coordinates. 817b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala * Coordinates are transformed in-place 827b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala * 837b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala * rects: A pointer to an array of consecutive (x,y, w, h) rectangles 847b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala * rectCount: Number of rectangles to transform 857b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala */ 867b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala status_t mapRawRectToCorrected(int32_t *rects, int rectCount); 877b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala 887b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala /** 897b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala * Transform from corrected (warped) to distorted (original) coordinates. 907b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala * Coordinates are transformed in-place 917b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala * 927b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala * coordPairs: A pointer to an array of consecutive (x,y) points 937b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala * coordCount: Number of (x,y) pairs to transform 947b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala */ 957b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala template<typename T> 967b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala status_t mapCorrectedToRaw(T* coordPairs, int coordCount) const; 977b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala 987b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala /** 997b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala * Transform from corrected (warped) to distorted (original) coordinates. 1007b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala * Coordinates are transformed in-place 1017b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala * 1027b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala * rects: A pointer to an array of consecutive (x,y, w, h) rectangles 1037b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala * rectCount: Number of rectangles to transform 1047b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala */ 1057b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala status_t mapCorrectedRectToRaw(int32_t *rects, int rectCount) const; 1067b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala 1077b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala struct GridQuad { 1087b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala // Source grid quad, or null 1097b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala const GridQuad *src; 1107b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala // x,y coordinates of corners, in 1117b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala // clockwise order 1127b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala std::array<float, 8> coords; 1137b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala }; 1147b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala 1157b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala // Find which grid quad encloses the point; returns null if none do 1167b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala static const GridQuad* findEnclosingQuad( 1177b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala const int32_t pt[2], const std::vector<GridQuad>& grid); 1187b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala 1197b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala // Calculate 'horizontal' interpolation coordinate for the point and the quad 1207b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala // Assumes the point P is within the quad Q. 1217b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala // Given quad with points P1-P4, and edges E12-E41, and considering the edge segments as 1227b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala // functions of U: E12(u), where E12(0) = P1 and E12(1) = P2, then we want to find a u 1237b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala // such that the edge E12(u) -> E43(u) contains point P. 1247b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala // This can be determined by checking if the cross product of vector [E12(u)-E43(u)] and 1257b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala // vector [E12(u)-P] is zero. Solving the equation 1267b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala // [E12(u)-E43(u)] x [E12(u)-P] = 0 gives a quadratic equation in u; the solution in the range 1277b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala // 0 to 1 is the one chosen. 1287b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala // If calculateU is true, then an interpolation coordinate for edges E12 and E43 is found; 1297b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala // if it is false, then an interpolation coordinate for edges E14 and E23 is found. 1307b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala static float calculateUorV(const int32_t pt[2], const GridQuad& quad, bool calculateU); 1317b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala 1327b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala private: 1337b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala mutable std::mutex mMutex; 1347b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala 1357b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala // Number of quads in each dimension of the mapping grids 1367b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala constexpr static size_t kGridSize = 15; 1377b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala // Margin to expand the grid by to ensure it doesn't clip the domain 1387b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala constexpr static float kGridMargin = 0.05f; 1397b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala // Fuzziness for float inequality tests 1407b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala constexpr static float kFloatFuzz = 1e-4; 1417b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala 1427b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala // Metadata key lists to correct 1437b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala 1447b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala // Both capture request and result 1457b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala static const std::array<uint32_t, 3> kMeteringRegionsToCorrect; 1467b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala 1477b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala // Only capture request 1487b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala static const std::array<uint32_t, 1> kRequestRectsToCorrect; 1497b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala 1507b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala // Only capture result 151cc073aac88a30dded339bc8809297fb78cc1b43aEino-Ville Talvala static const std::array<uint32_t, 1> kResultRectsToCorrect; 1527b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala 1537b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala // Only for capture results 154cc073aac88a30dded339bc8809297fb78cc1b43aEino-Ville Talvala static const std::array<uint32_t, 2> kResultPointsToCorrect; 1557b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala 1567b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala // Utility to create reverse mapping grids 1577b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala status_t buildGrids(); 1587b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala 1597b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala 1607b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala bool mValidMapping; 1617b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala bool mValidGrids; 1627b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala 1637b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala // intrisic parameters, in pixels 1647b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala float mFx, mFy, mCx, mCy, mS; 1657b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala // pre-calculated inverses for speed 1667b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala float mInvFx, mInvFy; 1677b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala // radial/tangential distortion parameters 1687b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala float mK[5]; 1697b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala 1707b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala // pre-correction active array dimensions 1717b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala int mArrayWidth, mArrayHeight; 172c3462a1b892911f35ba2ff53e0f7aaec38fc2e4dYin-Chia Yeh // active array dimensions 173c3462a1b892911f35ba2ff53e0f7aaec38fc2e4dYin-Chia Yeh int mActiveWidth, mActiveHeight; 1747b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala 1757b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala std::vector<GridQuad> mCorrectedGrid; 1767b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala std::vector<GridQuad> mDistortedGrid; 1777b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala 1787b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala}; // class DistortionMapper 1797b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala 1807b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala} // namespace camera3 1817b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala 1827b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala} // namespace android 1837b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala 1847b8a1fd27d12d1b3ea711b0edca6ff5b07f5beb1Eino-Ville Talvala#endif 185