1 // Copyright 2020 Google LLC
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 #include "guetzli_entry_points.h" // NOLINT(build/include)
16
17 #include <sys/stat.h>
18
19 #include <algorithm>
20 #include <cstdio>
21 #include <iostream>
22 #include <string>
23 #include <vector>
24
25 #include "guetzli/jpeg_data_reader.h"
26 #include "guetzli/quality.h"
27 #include "png.h" // NOLINT(build/include)
28 #include "absl/status/statusor.h"
29 #include "sandboxed_api/util/fileops.h"
30
31 namespace {
32
33 constexpr int kBytesPerPixel = 350;
34 constexpr int kLowestMemusageMB = 100;
35
36 struct GuetzliInitData {
37 std::string in_data;
38 guetzli::Params params;
39 guetzli::ProcessStats stats;
40 };
41
42 struct ImageData {
43 int xsize;
44 int ysize;
45 std::vector<uint8_t> rgb;
46 };
47
CreateLenValFromData(const void * data,size_t size)48 sapi::LenValStruct CreateLenValFromData(const void* data, size_t size) {
49 void* new_data = malloc(size);
50 memcpy(new_data, data, size);
51 return {size, new_data};
52 }
53
ReadFromFd(int fd)54 absl::StatusOr<std::string> ReadFromFd(int fd) {
55 struct stat file_data;
56 int status = fstat(fd, &file_data);
57
58 if (status < 0) {
59 return absl::FailedPreconditionError("Error reading input from fd");
60 }
61
62 std::string result;
63 result.resize(file_data.st_size);
64 status = read(fd, result.data(), result.size());
65
66 if (status < 0) {
67 return absl::FailedPreconditionError("Error reading input from fd");
68 }
69
70 return result;
71 }
72
PrepareDataForProcessing(const ProcessingParams & processing_params)73 absl::StatusOr<GuetzliInitData> PrepareDataForProcessing(
74 const ProcessingParams& processing_params) {
75 absl::StatusOr<std::string> input = ReadFromFd(processing_params.remote_fd);
76
77 if (!input.ok()) {
78 return input.status();
79 }
80
81 guetzli::Params guetzli_params;
82 guetzli_params.butteraugli_target = static_cast<float>(
83 guetzli::ButteraugliScoreForQuality(processing_params.quality));
84
85 guetzli::ProcessStats stats;
86
87 if (processing_params.verbose) {
88 stats.debug_output_file = stderr;
89 }
90
91 return GuetzliInitData{std::move(input.value()), guetzli_params, stats};
92 }
93
BlendOnBlack(const uint8_t val,const uint8_t alpha)94 inline uint8_t BlendOnBlack(const uint8_t val, const uint8_t alpha) {
95 return (static_cast<int>(val) * static_cast<int>(alpha) + 128) / 255;
96 }
97
98 // Modified version of ReadPNG from original guetzli.cc
ReadPNG(const std::string & data)99 absl::StatusOr<ImageData> ReadPNG(const std::string& data) {
100 std::vector<uint8_t> rgb;
101 int xsize, ysize;
102 png_structp png_ptr =
103 png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
104 if (!png_ptr) {
105 return absl::FailedPreconditionError(
106 "Error reading PNG data from input file");
107 }
108
109 png_infop info_ptr = png_create_info_struct(png_ptr);
110 if (!info_ptr) {
111 png_destroy_read_struct(&png_ptr, nullptr, nullptr);
112 return absl::FailedPreconditionError(
113 "Error reading PNG data from input file");
114 }
115
116 if (setjmp(png_jmpbuf(png_ptr)) != 0) {
117 // Ok we are here because of the setjmp.
118 png_destroy_read_struct(&png_ptr, &info_ptr, nullptr);
119 return absl::FailedPreconditionError(
120 "Error reading PNG data from input file");
121 }
122
123 std::istringstream memstream(data, std::ios::in | std::ios::binary);
124 png_set_read_fn(
125 png_ptr, static_cast<void*>(&memstream),
126 [](png_structp png_ptr, png_bytep outBytes, png_size_t byteCountToRead) {
127 std::istringstream& memstream =
128 *static_cast<std::istringstream*>(png_get_io_ptr(png_ptr));
129
130 memstream.read(reinterpret_cast<char*>(outBytes), byteCountToRead);
131
132 if (memstream.eof()) png_error(png_ptr, "unexpected end of data");
133 if (memstream.fail()) png_error(png_ptr, "read from memory error");
134 });
135
136 // The png_transforms flags are as follows:
137 // packing == convert 1,2,4 bit images,
138 // strip == 16 -> 8 bits / channel,
139 // shift == use sBIT dynamics, and
140 // expand == palettes -> rgb, grayscale -> 8 bit images, tRNS -> alpha.
141 const unsigned int png_transforms =
142 PNG_TRANSFORM_PACKING | PNG_TRANSFORM_EXPAND | PNG_TRANSFORM_STRIP_16;
143
144 png_read_png(png_ptr, info_ptr, png_transforms, nullptr);
145
146 png_bytep* row_pointers = png_get_rows(png_ptr, info_ptr);
147
148 xsize = png_get_image_width(png_ptr, info_ptr);
149 ysize = png_get_image_height(png_ptr, info_ptr);
150 rgb.resize(3 * xsize * ysize);
151
152 const int components = png_get_channels(png_ptr, info_ptr);
153 switch (components) {
154 case 1: {
155 // GRAYSCALE
156 for (int y = 0; y < ysize; ++y) {
157 const uint8_t* row_in = row_pointers[y];
158 uint8_t* row_out = &rgb[3 * y * xsize];
159 for (int x = 0; x < xsize; ++x) {
160 const uint8_t gray = row_in[x];
161 row_out[3 * x + 0] = gray;
162 row_out[3 * x + 1] = gray;
163 row_out[3 * x + 2] = gray;
164 }
165 }
166 break;
167 }
168 case 2: {
169 // GRAYSCALE + ALPHA
170 for (int y = 0; y < ysize; ++y) {
171 const uint8_t* row_in = row_pointers[y];
172 uint8_t* row_out = &rgb[3 * y * xsize];
173 for (int x = 0; x < xsize; ++x) {
174 const uint8_t gray = BlendOnBlack(row_in[2 * x], row_in[2 * x + 1]);
175 row_out[3 * x + 0] = gray;
176 row_out[3 * x + 1] = gray;
177 row_out[3 * x + 2] = gray;
178 }
179 }
180 break;
181 }
182 case 3: {
183 // RGB
184 for (int y = 0; y < ysize; ++y) {
185 const uint8_t* row_in = row_pointers[y];
186 uint8_t* row_out = &rgb[3 * y * xsize];
187 memcpy(row_out, row_in, 3 * xsize);
188 }
189 break;
190 }
191 case 4: {
192 // RGBA
193 for (int y = 0; y < ysize; ++y) {
194 const uint8_t* row_in = row_pointers[y];
195 uint8_t* row_out = &rgb[3 * y * xsize];
196 for (int x = 0; x < xsize; ++x) {
197 const uint8_t alpha = row_in[4 * x + 3];
198 row_out[3 * x + 0] = BlendOnBlack(row_in[4 * x + 0], alpha);
199 row_out[3 * x + 1] = BlendOnBlack(row_in[4 * x + 1], alpha);
200 row_out[3 * x + 2] = BlendOnBlack(row_in[4 * x + 2], alpha);
201 }
202 }
203 break;
204 }
205 default:
206 png_destroy_read_struct(&png_ptr, &info_ptr, nullptr);
207 return absl::FailedPreconditionError(
208 "Error reading PNG data from input file");
209 }
210 png_destroy_read_struct(&png_ptr, &info_ptr, nullptr);
211
212 return ImageData{xsize, ysize, std::move(rgb)};
213 }
214
CheckMemoryLimitExceeded(int memlimit_mb,int xsize,int ysize)215 bool CheckMemoryLimitExceeded(int memlimit_mb, int xsize, int ysize) {
216 double pixels = static_cast<double>(xsize) * ysize;
217 return memlimit_mb != -1 &&
218 (pixels * kBytesPerPixel / (1 << 20) > memlimit_mb ||
219 memlimit_mb < kLowestMemusageMB);
220 }
221
222 } // namespace
223
ProcessJpeg(const ProcessingParams * processing_params,sapi::LenValStruct * output)224 extern "C" bool ProcessJpeg(const ProcessingParams* processing_params,
225 sapi::LenValStruct* output) {
226 auto processing_data = PrepareDataForProcessing(*processing_params);
227
228 if (!processing_data.ok()) {
229 std::cerr << processing_data.status().ToString() << std::endl;
230 return false;
231 }
232
233 guetzli::JPEGData jpg_header;
234 if (!guetzli::ReadJpeg(processing_data->in_data, guetzli::JPEG_READ_HEADER,
235 &jpg_header)) {
236 std::cerr << "Error reading JPG data from input file" << std::endl;
237 return false;
238 }
239
240 if (CheckMemoryLimitExceeded(processing_params->memlimit_mb, jpg_header.width,
241 jpg_header.height)) {
242 std::cerr << "Memory limit would be exceeded" << std::endl;
243 return false;
244 }
245
246 std::string out_data;
247 if (!guetzli::Process(processing_data->params, &processing_data->stats,
248 processing_data->in_data, &out_data)) {
249 std::cerr << "Guezli processing failed" << std::endl;
250 return false;
251 }
252
253 *output = CreateLenValFromData(out_data.data(), out_data.size());
254 return true;
255 }
256
ProcessRgb(const ProcessingParams * processing_params,sapi::LenValStruct * output)257 extern "C" bool ProcessRgb(const ProcessingParams* processing_params,
258 sapi::LenValStruct* output) {
259 auto processing_data = PrepareDataForProcessing(*processing_params);
260
261 if (!processing_data.ok()) {
262 std::cerr << processing_data.status().ToString() << std::endl;
263 return false;
264 }
265
266 auto png_data = ReadPNG(processing_data->in_data);
267 if (!png_data.ok()) {
268 std::cerr << "Error reading PNG data from input file" << std::endl;
269 return false;
270 }
271
272 if (CheckMemoryLimitExceeded(processing_params->memlimit_mb, png_data->xsize,
273 png_data->ysize)) {
274 std::cerr << "Memory limit would be exceeded" << std::endl;
275 return false;
276 }
277
278 std::string out_data;
279 if (!guetzli::Process(processing_data->params, &processing_data->stats,
280 png_data->rgb, png_data->xsize, png_data->ysize,
281 &out_data)) {
282 std::cerr << "Guetzli processing failed" << std::endl;
283 return false;
284 }
285
286 *output = CreateLenValFromData(out_data.data(), out_data.size());
287 return true;
288 }
289
WriteDataToFd(int fd,sapi::LenValStruct * data)290 extern "C" bool WriteDataToFd(int fd, sapi::LenValStruct* data) {
291 return sandbox2::file_util::fileops::WriteToFD(
292 fd, static_cast<const char*>(data->data), data->size);
293 }
294