1 /*
2  * Copyright 2016 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 //#define LOG_NDEBUG 0
17 #define LOG_TAG "HdrPlusClientUtils"
18 #include <log/log.h>
19 
20 #include <fstream>
21 #include <inttypes.h>
22 #include <system/graphics.h>
23 
24 #include "HdrPlusClientUtils.h"
25 
26 namespace android {
27 namespace hdrplus_client_utils {
28 
29 // Get the RGB values of the pixel at (x, y).
getRgb(uint8_t * r,uint8_t * g,uint8_t * b,uint32_t x,uint32_t y,const pbcamera::StreamConfiguration & streamConfig,const pbcamera::StreamBuffer & buffer)30 static status_t getRgb(uint8_t *r, uint8_t *g, uint8_t* b, uint32_t x, uint32_t y,
31         const pbcamera::StreamConfiguration &streamConfig,
32         const pbcamera::StreamBuffer &buffer) {
33     switch (streamConfig.image.format) {
34         case HAL_PIXEL_FORMAT_YCrCb_420_SP:
35         {
36             // Check the stream configuration has two planes.
37             if (streamConfig.image.planes.size() != 2) {
38                 ALOGE("%s: NV21 should have 2 planes but it has %zu", __FUNCTION__,
39                         streamConfig.image.planes.size());
40                 return BAD_VALUE;
41             }
42 
43             // Find the indices of Y, V, and U in the buffer.
44             uint32_t yIndex = y * streamConfig.image.planes[0].stride + x;
45             uint32_t vIndex = streamConfig.image.planes[0].scanline *
46                               streamConfig.image.planes[0].stride +
47                               (y / 2) * streamConfig.image.planes[1].stride + (x & ~0x1);
48             uint32_t uIndex = vIndex + 1;
49 
50             // Convert YUV to RGB.
51             int32_t yc = ((uint8_t*)buffer.data)[yIndex];
52             int32_t vc = ((uint8_t*)buffer.data)[vIndex] - 128;
53             int32_t uc = ((uint8_t*)buffer.data)[uIndex] - 128;
54             *r = std::min(std::max(yc + 0.003036f * uc + 1.399457f * vc, 0.0f), 255.0f);
55             *g = std::min(std::max(yc - 0.344228f * uc - 0.717202f * vc, 0.0f), 255.0f);
56             *b = std::min(std::max(yc + 1.772431f * uc - 0.006137f * vc, 0.0f), 255.0f);
57             return OK;
58         }
59         case HAL_PIXEL_FORMAT_RGB_888:
60         {
61             // Check the stream configuration has 1 plane.
62             if (streamConfig.image.planes.size() != 1) {
63                 ALOGE("%s: RGB_888 should have 1 plane but it has %zu", __FUNCTION__,
64                         streamConfig.image.planes.size());
65                 return BAD_VALUE;
66             }
67 
68             uint32_t offset = y * streamConfig.image.planes[0].stride + x * 3;
69             *r = ((uint8_t*)buffer.data)[offset];
70             *g = ((uint8_t*)buffer.data)[offset + 1];
71             *b = ((uint8_t*)buffer.data)[offset + 2];
72             return OK;
73         }
74         default:
75             ALOGE("%s: Format %d is not supported.", __FUNCTION__, streamConfig.image.format);
76             return BAD_VALUE;
77     }
78 }
79 
writePpm(const std::string & filename,const pbcamera::StreamConfiguration & streamConfig,const pbcamera::StreamBuffer & buffer)80 status_t writePpm(const std::string &filename, const pbcamera::StreamConfiguration &streamConfig,
81         const pbcamera::StreamBuffer &buffer) {
82     if (streamConfig.image.format != HAL_PIXEL_FORMAT_YCrCb_420_SP &&
83             streamConfig.image.format != HAL_PIXEL_FORMAT_RGB_888) {
84         ALOGE("%s: format 0x%x is not supported.", __FUNCTION__, streamConfig.image.format);
85         return BAD_VALUE;
86     }
87 
88     std::ofstream outfile(filename, std::ios::binary);
89     if (!outfile.is_open()) {
90         ALOGE("%s: Opening file (%s) failed.", __FUNCTION__, filename.data());
91         return NO_INIT;
92     }
93 
94     uint32_t width = streamConfig.image.width;
95     uint32_t height = streamConfig.image.height;
96 
97     // Write headers of the ppm file.
98     outfile << "P6";
99     outfile << " " << std::to_string(width) << " " << std::to_string(height) << " 255 ";
100 
101     // Write RGB values of the image.
102     uint8_t r, g, b;
103     for (uint32_t y = 0; y < height; y++) {
104         for (uint32_t x = 0; x < width; x++) {
105             status_t res = getRgb(&r, &g, &b, x, y, streamConfig, buffer);
106             if (res != OK) {
107                 ALOGE("%s: Getting RGB failed: %s (%d).", __FUNCTION__, strerror(-res), res);
108                 return res;
109             }
110             outfile << r << g << b;
111         }
112     }
113 
114     ALOGD("%s: Saved file: %s", __FUNCTION__, filename.data());
115 
116     outfile.close();
117     return OK;
118 }
119 
comparePpm(const std::string & filename,const pbcamera::StreamConfiguration & streamConfig,const pbcamera::StreamBuffer & buffer,float * diffRatio)120 status_t comparePpm(const std::string &filename, const pbcamera::StreamConfiguration &streamConfig,
121         const pbcamera::StreamBuffer &buffer, float *diffRatio) {
122     if (streamConfig.image.format != HAL_PIXEL_FORMAT_YCrCb_420_SP) {
123         ALOGE("%s: format 0x%x is not supported.", __FUNCTION__, streamConfig.image.format);
124         return BAD_VALUE;
125     }
126 
127     std::ifstream ifile(filename, std::ios::binary);
128     if (!ifile.is_open()) {
129         ALOGE("%s: Opening file (%s) failed.", __FUNCTION__, filename.data());
130         return NO_INIT;
131     }
132 
133     std::string s;
134 
135     // Read headers of the ppm file.
136     ifile >> s;
137     if (s != "P6") {
138         ALOGE("%s: Invalid PPM file header: %s", __FUNCTION__, s.c_str());
139         return BAD_VALUE;
140     }
141 
142     // Read width and height.
143     ifile >> s;
144     uint32_t width = std::stoul(s);
145 
146     ifile >> s;
147     uint32_t height = std::stoul(s);
148 
149     if (width != streamConfig.image.width || height != streamConfig.image.height) {
150         ALOGE("%s: Image resolution doesn't match. image %dx%d ppm %dx%d",
151                 __FUNCTION__, streamConfig.image.width, streamConfig.image.height,
152                 width, height);
153         return BAD_VALUE;
154     }
155 
156     ifile >> s;
157     if (s != "255") {
158         ALOGE("%s: Expecting 255 but got %s", __FUNCTION__, s.c_str());
159         return BAD_VALUE;
160     }
161 
162     char c;
163 
164     // Get a space
165     ifile.get(c);
166 
167     // Now the RGB values start.
168     uint8_t r, g, b;
169     uint64_t diff = 0;
170 
171     for (uint32_t y = 0; y < height; y++) {
172         for (uint32_t x = 0; x < width; x++) {
173             status_t res = getRgb(&r, &g, &b, x, y, streamConfig, buffer);
174             if (res != OK) {
175                 ALOGE("%s: Getting RGB failed: %s (%d).", __FUNCTION__, strerror(-res), res);
176                 return res;
177             }
178 
179             // Get r, g, b from golden image and accumulate the differences.
180             ifile.get(c);
181             diff += abs(static_cast<int32_t>(c) - r);
182             ifile.get(c);
183             diff += abs(static_cast<int32_t>(c) - g);
184             ifile.get(c);
185             diff += abs(static_cast<int32_t>(c) - b);
186         }
187     }
188 
189     if (diffRatio != nullptr) {
190         *diffRatio = diff / (static_cast<float>(width) * height * 3 * 256);
191     }
192 
193     return OK;
194 }
195 
196 } // hdrplus_client_utils
197 } // namespace android
198