1 // Copyright 2016 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "services/data_decoder/image_decoder_impl.h"
6 
7 #include <string.h>
8 
9 #include <utility>
10 
11 #include "base/logging.h"
12 #include "mojo/public/cpp/bindings/strong_binding.h"
13 #include "skia/ext/image_operations.h"
14 #include "third_party/blink/public/platform/web_data.h"
15 #include "third_party/blink/public/platform/web_size.h"
16 #include "third_party/blink/public/web/web_image.h"
17 #include "third_party/skia/include/core/SkBitmap.h"
18 
19 #if defined(OS_CHROMEOS)
20 #include "ui/gfx/codec/png_codec.h"
21 #endif
22 
23 namespace data_decoder {
24 
25 namespace {
26 
27 int64_t kPadding = 64;
28 
ResizeImage(SkBitmap * decoded_image,bool shrink_to_fit,int64_t max_size_in_bytes)29 void ResizeImage(SkBitmap* decoded_image,
30                  bool shrink_to_fit,
31                  int64_t max_size_in_bytes) {
32   // When serialized, the space taken up by a skia::mojom::Bitmap excluding
33   // the pixel data payload should be:
34   //   sizeof(skia::mojom::Bitmap::Data_) + pixel data array header (8 bytes)
35   // Use a bigger number in case we need padding at the end.
36   int64_t struct_size = sizeof(skia::mojom::Bitmap::Data_) + kPadding;
37   int64_t image_size = decoded_image->computeByteSize();
38   int halves = 0;
39   while (struct_size + (image_size >> 2 * halves) > max_size_in_bytes)
40     halves++;
41   if (halves) {
42     // If the decoded image is too large, either discard it or shrink it.
43     //
44     // TODO(rockot): Also support exposing the bytes via shared memory for
45     // larger images. https://crbug.com/416916.
46     if (shrink_to_fit) {
47       // Shrinking by halves prevents quality loss and should never
48       // overshrink on displays smaller than 3600x2400.
49       *decoded_image = skia::ImageOperations::Resize(
50           *decoded_image, skia::ImageOperations::RESIZE_LANCZOS3,
51           decoded_image->width() >> halves, decoded_image->height() >> halves);
52     } else {
53       decoded_image->reset();
54     }
55   }
56 }
57 
58 }  // namespace
59 
60 ImageDecoderImpl::ImageDecoderImpl() = default;
61 
62 ImageDecoderImpl::~ImageDecoderImpl() = default;
63 
DecodeImage(const std::vector<uint8_t> & encoded_data,mojom::ImageCodec codec,bool shrink_to_fit,int64_t max_size_in_bytes,const gfx::Size & desired_image_frame_size,DecodeImageCallback callback)64 void ImageDecoderImpl::DecodeImage(const std::vector<uint8_t>& encoded_data,
65                                    mojom::ImageCodec codec,
66                                    bool shrink_to_fit,
67                                    int64_t max_size_in_bytes,
68                                    const gfx::Size& desired_image_frame_size,
69                                    DecodeImageCallback callback) {
70   if (encoded_data.size() == 0) {
71     std::move(callback).Run(SkBitmap());
72     return;
73   }
74 
75   SkBitmap decoded_image;
76 #if defined(OS_CHROMEOS)
77   if (codec == mojom::ImageCodec::ROBUST_PNG) {
78     // Our robust PNG decoding is using libpng.
79     if (encoded_data.size()) {
80       SkBitmap decoded_png;
81       if (gfx::PNGCodec::Decode(encoded_data.data(), encoded_data.size(),
82                                 &decoded_png)) {
83         decoded_image = decoded_png;
84       }
85     }
86   }
87 #endif  // defined(OS_CHROMEOS)
88   if (codec == mojom::ImageCodec::DEFAULT) {
89     decoded_image = blink::WebImage::FromData(
90         blink::WebData(reinterpret_cast<const char*>(encoded_data.data()),
91                        encoded_data.size()),
92         desired_image_frame_size);
93   }
94 
95   if (!decoded_image.isNull())
96     ResizeImage(&decoded_image, shrink_to_fit, max_size_in_bytes);
97 
98   std::move(callback).Run(decoded_image);
99 }
100 
DecodeAnimation(const std::vector<uint8_t> & encoded_data,bool shrink_to_fit,int64_t max_size_in_bytes,DecodeAnimationCallback callback)101 void ImageDecoderImpl::DecodeAnimation(const std::vector<uint8_t>& encoded_data,
102                                        bool shrink_to_fit,
103                                        int64_t max_size_in_bytes,
104                                        DecodeAnimationCallback callback) {
105   if (encoded_data.size() == 0) {
106     std::move(callback).Run(std::vector<mojom::AnimationFramePtr>());
107     return;
108   }
109 
110   auto frames = blink::WebImage::AnimationFromData(blink::WebData(
111       reinterpret_cast<const char*>(encoded_data.data()), encoded_data.size()));
112 
113   int64_t max_frame_size_in_bytes = max_size_in_bytes / frames.size();
114   std::vector<mojom::AnimationFramePtr> decoded_images;
115 
116   for (const blink::WebImage::AnimationFrame& frame : frames) {
117     auto image_frame = mojom::AnimationFrame::New();
118     image_frame->bitmap = frame.bitmap;
119     image_frame->duration = frame.duration;
120 
121     ResizeImage(&image_frame->bitmap, shrink_to_fit, max_frame_size_in_bytes);
122     // Resizing reset the frame because it was too large. Clear out any
123     // previously decoded frames so we do not return a partially decoded image.
124     if (image_frame->bitmap.isNull()) {
125       decoded_images.clear();
126       break;
127     }
128 
129     decoded_images.push_back(std::move(image_frame));
130   }
131 
132   std::move(callback).Run(std::move(decoded_images));
133 }
134 
135 }  // namespace data_decoder
136