| OLD | NEW |
| (Empty) |
| 1 /* | |
| 2 * Copyright (c) 2010, Google Inc. All rights reserved. | |
| 3 * | |
| 4 * Redistribution and use in source and binary forms, with or without | |
| 5 * modification, are permitted provided that the following conditions are | |
| 6 * met: | |
| 7 * | |
| 8 * * Redistributions of source code must retain the above copyright | |
| 9 * notice, this list of conditions and the following disclaimer. | |
| 10 * * Redistributions in binary form must reproduce the above | |
| 11 * copyright notice, this list of conditions and the following disclaimer | |
| 12 * in the documentation and/or other materials provided with the | |
| 13 * distribution. | |
| 14 * * Neither the name of Google Inc. nor the names of its | |
| 15 * contributors may be used to endorse or promote products derived from | |
| 16 * this software without specific prior written permission. | |
| 17 * | |
| 18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 29 */ | |
| 30 | |
| 31 #include "platform/image-encoders/JPEGImageEncoder.h" | |
| 32 | |
| 33 #include <memory> | |
| 34 #include "SkColorPriv.h" | |
| 35 #include "platform/geometry/IntSize.h" | |
| 36 #include "platform/graphics/ImageBuffer.h" | |
| 37 #include "platform/image-encoders/RGBAtoRGB.h" | |
| 38 #include "platform/wtf/CurrentTime.h" | |
| 39 #include "platform/wtf/PtrUtil.h" | |
| 40 | |
| 41 extern "C" { | |
| 42 #include <setjmp.h> | |
| 43 #include <stdio.h> // jpeglib.h needs stdio.h FILE | |
| 44 #include "jpeglib.h" | |
| 45 } | |
| 46 | |
| 47 namespace blink { | |
| 48 | |
| 49 void RGBAtoRGBScalar(const unsigned char* pixels, | |
| 50 unsigned pixel_count, | |
| 51 unsigned char* output) { | |
| 52 // Per <canvas> spec, composite the input image pixels source-over on black. | |
| 53 for (; pixel_count-- > 0; pixels += 4) { | |
| 54 unsigned char alpha = pixels[3]; | |
| 55 if (alpha != 255) { | |
| 56 *output++ = SkMulDiv255Round(pixels[0], alpha); | |
| 57 *output++ = SkMulDiv255Round(pixels[1], alpha); | |
| 58 *output++ = SkMulDiv255Round(pixels[2], alpha); | |
| 59 } else { | |
| 60 *output++ = pixels[0]; | |
| 61 *output++ = pixels[1]; | |
| 62 *output++ = pixels[2]; | |
| 63 } | |
| 64 } | |
| 65 } | |
| 66 | |
| 67 // TODO(cavalcantii): use regular macro, see https://crbug.com/673067. | |
| 68 #ifdef __ARM_NEON__ | |
| 69 void RGBAtoRGBNeon(const unsigned char* input, | |
| 70 const unsigned pixel_count, | |
| 71 unsigned char* output) { | |
| 72 const unsigned kPixelsPerLoad = 16; | |
| 73 const unsigned kRgbaStep = kPixelsPerLoad * 4, kRgbStep = kPixelsPerLoad * 3; | |
| 74 // Input registers. | |
| 75 uint8x16x4_t rgba; | |
| 76 // Output registers. | |
| 77 uint8x16x3_t rgb; | |
| 78 // Intermediate registers. | |
| 79 uint8x8_t low, high; | |
| 80 uint8x16_t result; | |
| 81 unsigned counter; | |
| 82 auto transform_color = [&](size_t channel) { | |
| 83 // Extracts the low/high part of the 128 bits. | |
| 84 low = vget_low_u8(rgba.val[channel]); | |
| 85 high = vget_high_u8(rgba.val[channel]); | |
| 86 // Scale the color and combine. | |
| 87 uint16x8_t temp = vmull_u8(low, vget_low_u8(rgba.val[3])); | |
| 88 low = vraddhn_u16(temp, vrshrq_n_u16(temp, 8)); | |
| 89 temp = vmull_u8(high, vget_high_u8(rgba.val[3])); | |
| 90 high = vraddhn_u16(temp, vrshrq_n_u16(temp, 8)); | |
| 91 result = vcombine_u8(low, high); | |
| 92 // Write back the channel to a 128 bits register. | |
| 93 rgb.val[channel] = result; | |
| 94 }; | |
| 95 | |
| 96 for (counter = 0; counter + kPixelsPerLoad <= pixel_count; | |
| 97 counter += kPixelsPerLoad) { | |
| 98 // Reads 16 pixels at once, each color channel in a different | |
| 99 // 128 bits register. | |
| 100 rgba = vld4q_u8(input); | |
| 101 | |
| 102 transform_color(0); | |
| 103 transform_color(1); | |
| 104 transform_color(2); | |
| 105 | |
| 106 // Write back (interleaved) results to output. | |
| 107 vst3q_u8(output, rgb); | |
| 108 | |
| 109 // Advance to next elements (could be avoided loading register with | |
| 110 // increment after i.e. "vld4 {vector}, [r1]!"). | |
| 111 input += kRgbaStep; | |
| 112 output += kRgbStep; | |
| 113 } | |
| 114 | |
| 115 // Handle the tail elements. | |
| 116 unsigned remaining = pixel_count; | |
| 117 remaining -= counter; | |
| 118 if (remaining != 0) { | |
| 119 RGBAtoRGBScalar(input, remaining, output); | |
| 120 } | |
| 121 } | |
| 122 #endif | |
| 123 | |
| 124 struct JPEGOutputBuffer : public jpeg_destination_mgr { | |
| 125 DISALLOW_NEW(); | |
| 126 Vector<unsigned char>* output; | |
| 127 Vector<unsigned char> buffer; | |
| 128 }; | |
| 129 | |
| 130 class JPEGImageEncoderStateImpl final : public JPEGImageEncoderState { | |
| 131 public: | |
| 132 JPEGImageEncoderStateImpl() {} | |
| 133 ~JPEGImageEncoderStateImpl() override { | |
| 134 jpeg_destroy_compress(&cinfo_); | |
| 135 cinfo_.client_data = 0; | |
| 136 } | |
| 137 JPEGOutputBuffer* OutputBuffer() { return &output_buffer_; } | |
| 138 jpeg_compress_struct* Cinfo() { return &cinfo_; } | |
| 139 jpeg_error_mgr* GetError() { return &error_; } | |
| 140 | |
| 141 private: | |
| 142 JPEGOutputBuffer output_buffer_; | |
| 143 jpeg_compress_struct cinfo_; | |
| 144 jpeg_error_mgr error_; | |
| 145 }; | |
| 146 | |
| 147 static void PrepareOutput(j_compress_ptr cinfo) { | |
| 148 JPEGOutputBuffer* out = static_cast<JPEGOutputBuffer*>(cinfo->dest); | |
| 149 const size_t kInternalBufferSize = 8192; | |
| 150 out->buffer.resize(kInternalBufferSize); | |
| 151 out->next_output_byte = out->buffer.data(); | |
| 152 out->free_in_buffer = out->buffer.size(); | |
| 153 } | |
| 154 | |
| 155 static boolean WriteOutput(j_compress_ptr cinfo) { | |
| 156 JPEGOutputBuffer* out = static_cast<JPEGOutputBuffer*>(cinfo->dest); | |
| 157 out->output->Append(out->buffer.data(), out->buffer.size()); | |
| 158 out->next_output_byte = out->buffer.data(); | |
| 159 out->free_in_buffer = out->buffer.size(); | |
| 160 return TRUE; | |
| 161 } | |
| 162 | |
| 163 static void FinishOutput(j_compress_ptr cinfo) { | |
| 164 JPEGOutputBuffer* out = static_cast<JPEGOutputBuffer*>(cinfo->dest); | |
| 165 const size_t size = out->buffer.size() - out->free_in_buffer; | |
| 166 out->output->Append(out->buffer.data(), size); | |
| 167 } | |
| 168 | |
| 169 static void HandleError(j_common_ptr common) { | |
| 170 jmp_buf* jump_buffer_ptr = static_cast<jmp_buf*>(common->client_data); | |
| 171 longjmp(*jump_buffer_ptr, -1); | |
| 172 } | |
| 173 | |
| 174 static void DisableSubsamplingForHighQuality(jpeg_compress_struct* cinfo, | |
| 175 int quality) { | |
| 176 if (quality < 100) | |
| 177 return; | |
| 178 | |
| 179 for (int i = 0; i < MAX_COMPONENTS; ++i) { | |
| 180 cinfo->comp_info[i].h_samp_factor = 1; | |
| 181 cinfo->comp_info[i].v_samp_factor = 1; | |
| 182 } | |
| 183 } | |
| 184 | |
| 185 #define SET_JUMP_BUFFER(jpeg_compress_struct_ptr, what_to_return) \ | |
| 186 jmp_buf jump_buffer; \ | |
| 187 jpeg_compress_struct_ptr->client_data = &jump_buffer; \ | |
| 188 if (setjmp(jump_buffer)) { \ | |
| 189 return what_to_return; \ | |
| 190 } | |
| 191 | |
| 192 std::unique_ptr<JPEGImageEncoderState> JPEGImageEncoderState::Create( | |
| 193 const IntSize& image_size, | |
| 194 const double& quality, | |
| 195 Vector<unsigned char>* output) { | |
| 196 if (image_size.Width() <= 0 || image_size.Height() <= 0) | |
| 197 return nullptr; | |
| 198 | |
| 199 std::unique_ptr<JPEGImageEncoderStateImpl> encoder_state = | |
| 200 WTF::MakeUnique<JPEGImageEncoderStateImpl>(); | |
| 201 | |
| 202 jpeg_compress_struct* cinfo = encoder_state->Cinfo(); | |
| 203 jpeg_error_mgr* error = encoder_state->GetError(); | |
| 204 cinfo->err = jpeg_std_error(error); | |
| 205 error->error_exit = HandleError; | |
| 206 | |
| 207 SET_JUMP_BUFFER(cinfo, nullptr); | |
| 208 | |
| 209 JPEGOutputBuffer* destination = encoder_state->OutputBuffer(); | |
| 210 destination->output = output; | |
| 211 | |
| 212 jpeg_create_compress(cinfo); | |
| 213 cinfo->dest = destination; | |
| 214 cinfo->dest->init_destination = PrepareOutput; | |
| 215 cinfo->dest->empty_output_buffer = WriteOutput; | |
| 216 cinfo->dest->term_destination = FinishOutput; | |
| 217 | |
| 218 cinfo->image_height = image_size.Height(); | |
| 219 cinfo->image_width = image_size.Width(); | |
| 220 cinfo->in_color_space = JCS_RGB; | |
| 221 cinfo->input_components = 3; | |
| 222 | |
| 223 jpeg_set_defaults(cinfo); | |
| 224 int compression_quality = | |
| 225 JPEGImageEncoder::ComputeCompressionQuality(quality); | |
| 226 jpeg_set_quality(cinfo, compression_quality, TRUE); | |
| 227 DisableSubsamplingForHighQuality(cinfo, compression_quality); | |
| 228 jpeg_start_compress(cinfo, TRUE); | |
| 229 | |
| 230 cinfo->client_data = 0; | |
| 231 return std::move(encoder_state); | |
| 232 } | |
| 233 | |
| 234 int JPEGImageEncoder::ComputeCompressionQuality(const double& quality) { | |
| 235 int compression_quality = JPEGImageEncoder::kDefaultCompressionQuality; | |
| 236 if (quality >= 0.0 && quality <= 1.0) | |
| 237 compression_quality = static_cast<int>(quality * 100 + 0.5); | |
| 238 return compression_quality; | |
| 239 } | |
| 240 | |
| 241 int JPEGImageEncoder::ProgressiveEncodeRowsJpegHelper( | |
| 242 JPEGImageEncoderState* encoder_state, | |
| 243 unsigned char* data, | |
| 244 int current_rows_completed, | |
| 245 const double slack_before_deadline, | |
| 246 double deadline_seconds) { | |
| 247 JPEGImageEncoderStateImpl* encoder_state_impl = | |
| 248 static_cast<JPEGImageEncoderStateImpl*>(encoder_state); | |
| 249 Vector<JSAMPLE> row(encoder_state_impl->Cinfo()->image_width * | |
| 250 encoder_state_impl->Cinfo()->input_components); | |
| 251 SET_JUMP_BUFFER(encoder_state_impl->Cinfo(), kProgressiveEncodeFailed); | |
| 252 | |
| 253 const size_t pixel_row_stride = encoder_state_impl->Cinfo()->image_width * 4; | |
| 254 unsigned char* pixels = data + pixel_row_stride * current_rows_completed; | |
| 255 | |
| 256 while (encoder_state_impl->Cinfo()->next_scanline < | |
| 257 encoder_state_impl->Cinfo()->image_height) { | |
| 258 JSAMPLE* row_data = row.data(); | |
| 259 RGBAtoRGB(pixels, encoder_state_impl->Cinfo()->image_width, row_data); | |
| 260 jpeg_write_scanlines(encoder_state_impl->Cinfo(), &row_data, 1); | |
| 261 pixels += pixel_row_stride; | |
| 262 current_rows_completed++; | |
| 263 | |
| 264 if (deadline_seconds - slack_before_deadline - | |
| 265 MonotonicallyIncreasingTime() <= | |
| 266 0) { | |
| 267 return current_rows_completed; | |
| 268 } | |
| 269 } | |
| 270 | |
| 271 jpeg_finish_compress(encoder_state_impl->Cinfo()); | |
| 272 return current_rows_completed; | |
| 273 } | |
| 274 | |
| 275 bool JPEGImageEncoder::EncodeWithPreInitializedState( | |
| 276 std::unique_ptr<JPEGImageEncoderState> encoder_state, | |
| 277 const unsigned char* input_pixels, | |
| 278 int num_rows_completed) { | |
| 279 JPEGImageEncoderStateImpl* encoder_state_impl = | |
| 280 static_cast<JPEGImageEncoderStateImpl*>(encoder_state.get()); | |
| 281 | |
| 282 Vector<JSAMPLE> row; | |
| 283 row.resize(encoder_state_impl->Cinfo()->image_width * | |
| 284 encoder_state_impl->Cinfo()->input_components); | |
| 285 | |
| 286 SET_JUMP_BUFFER(encoder_state_impl->Cinfo(), false); | |
| 287 | |
| 288 const size_t pixel_row_stride = encoder_state_impl->Cinfo()->image_width * 4; | |
| 289 unsigned char* pixels = const_cast<unsigned char*>(input_pixels) + | |
| 290 pixel_row_stride * num_rows_completed; | |
| 291 while (encoder_state_impl->Cinfo()->next_scanline < | |
| 292 encoder_state_impl->Cinfo()->image_height) { | |
| 293 JSAMPLE* row_data = row.data(); | |
| 294 RGBAtoRGB(pixels, encoder_state_impl->Cinfo()->image_width, row_data); | |
| 295 jpeg_write_scanlines(encoder_state_impl->Cinfo(), &row_data, 1); | |
| 296 pixels += pixel_row_stride; | |
| 297 } | |
| 298 | |
| 299 jpeg_finish_compress(encoder_state_impl->Cinfo()); | |
| 300 return true; | |
| 301 } | |
| 302 | |
| 303 bool JPEGImageEncoder::Encode(const ImageDataBuffer& image_data, | |
| 304 const double& quality, | |
| 305 Vector<unsigned char>* output) { | |
| 306 if (!image_data.Pixels()) | |
| 307 return false; | |
| 308 | |
| 309 std::unique_ptr<JPEGImageEncoderState> encoder_state = | |
| 310 JPEGImageEncoderState::Create(image_data.size(), quality, output); | |
| 311 if (!encoder_state) | |
| 312 return false; | |
| 313 | |
| 314 return JPEGImageEncoder::EncodeWithPreInitializedState( | |
| 315 std::move(encoder_state), image_data.Pixels()); | |
| 316 } | |
| 317 | |
| 318 } // namespace blink | |
| OLD | NEW |