| // Copyright 2021 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "ash/public/cpp/image_util.h" |
| |
| #include <algorithm> |
| #include <memory> |
| #include <string> |
| |
| #include "base/files/file_util.h" |
| #include "base/functional/bind.h" |
| #include "base/functional/callback.h" |
| #include "base/task/task_traits.h" |
| #include "base/task/thread_pool.h" |
| #include "ipc/constants.mojom.h" |
| #include "services/data_decoder/public/cpp/decode_image.h" |
| #include "ui/gfx/image/canvas_image_source.h" |
| #include "ui/gfx/image/image_skia.h" |
| #include "ui/gfx/image/image_skia_operations.h" |
| |
| namespace ash { |
| namespace image_util { |
| namespace { |
| |
| const int64_t kMaxImageSizeInBytes = |
| static_cast<int64_t>(IPC::mojom::kChannelMaximumMessageSize); |
| |
| std::string ReadFileToString(const base::FilePath& path) { |
| std::string result; |
| if (!base::ReadFileToString(path, &result)) { |
| LOG(WARNING) << "Failed reading file"; |
| result.clear(); |
| } |
| |
| return result; |
| } |
| |
| void ToImageSkia(DecodeImageCallback callback, const SkBitmap& bitmap) { |
| if (bitmap.empty()) { |
| LOG(WARNING) << "Failed to decode image"; |
| std::move(callback).Run(gfx::ImageSkia()); |
| return; |
| } |
| gfx::ImageSkia image = gfx::ImageSkia::CreateFrom1xBitmap(bitmap); |
| std::move(callback).Run(image); |
| } |
| |
| void ToFrames(DecodeAnimationCallback callback, |
| std::vector<data_decoder::mojom::AnimationFramePtr> raw_frames) { |
| std::vector<AnimationFrame> frames(raw_frames.size()); |
| std::ranges::transform( |
| raw_frames, frames.begin(), |
| [](const data_decoder::mojom::AnimationFramePtr& frame_ptr) { |
| return AnimationFrame{ |
| gfx::ImageSkia::CreateFrom1xBitmap(frame_ptr->bitmap), |
| frame_ptr->duration}; |
| }); |
| std::move(callback).Run(std::move(frames)); |
| } |
| |
| void ScheduleFileRead( |
| const base::FilePath& file_path, |
| scoped_refptr<base::SequencedTaskRunner> file_task_runner, |
| base::OnceCallback<void(const std::string&)> completion_cb) { |
| if (!file_task_runner) { |
| file_task_runner = base::ThreadPool::CreateSequencedTaskRunner( |
| {base::MayBlock(), base::TaskPriority::USER_VISIBLE, |
| base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN}); |
| } |
| file_task_runner->PostTaskAndReplyWithResult( |
| FROM_HERE, base::BindOnce(&ReadFileToString, file_path), |
| std::move(completion_cb)); |
| } |
| |
| // EmptyImageSkiaSource -------------------------------------------------------- |
| |
| // An `gfx::ImageSkiaSource` which draws nothing to its `canvas`. |
| class EmptyImageSkiaSource : public gfx::CanvasImageSource { |
| public: |
| explicit EmptyImageSkiaSource(const gfx::Size& size) |
| : gfx::CanvasImageSource(size) {} |
| |
| EmptyImageSkiaSource(const EmptyImageSkiaSource&) = delete; |
| EmptyImageSkiaSource& operator=(const EmptyImageSkiaSource&) = delete; |
| ~EmptyImageSkiaSource() override = default; |
| |
| private: |
| // gfx::CanvasImageSource: |
| void Draw(gfx::Canvas* canvas) override {} // Draw nothing. |
| }; |
| |
| } // namespace |
| |
| // Utilities ------------------------------------------------------------------- |
| |
| gfx::ImageSkia CreateEmptyImage(const gfx::Size& size) { |
| return gfx::ImageSkia(std::make_unique<EmptyImageSkiaSource>(size), size); |
| } |
| |
| gfx::ImageSkia ResizeAndCropImage(const gfx::ImageSkia& image_skia, |
| const gfx::Size& new_size) { |
| // Calculate the scale factors necessary to make each axis match its |
| // respective part of `new_size`. |
| const float scale_x = |
| new_size.width() / static_cast<float>(image_skia.width()); |
| const float scale_y = |
| new_size.height() / static_cast<float>(image_skia.height()); |
| |
| // Whichever scale factor is larger is what we want to use, so that we are |
| // cropping excess instead of leaving empty space. |
| const float scale = std::max(scale_x, scale_y); |
| |
| // Scale the image to the size that this scale factor indicates. |
| auto resized_image_skia = gfx::ImageSkiaOperations::CreateResizedImage( |
| image_skia, skia::ImageOperations::ResizeMethod::RESIZE_BEST, |
| gfx::ScaleToCeiledSize(image_skia.size(), scale)); |
| |
| // Crop any excess outside the bounds. |
| gfx::Rect cropped_bounds(resized_image_skia.size()); |
| cropped_bounds.ClampToCenteredSize(new_size); |
| return gfx::ImageSkiaOperations::ExtractSubset(resized_image_skia, |
| cropped_bounds); |
| } |
| |
| void DecodeImageFile( |
| DecodeImageCallback callback, |
| const base::FilePath& file_path, |
| data_decoder::mojom::ImageCodec codec, |
| scoped_refptr<base::SequencedTaskRunner> file_task_runner) { |
| ScheduleFileRead( |
| file_path, std::move(file_task_runner), |
| base::BindOnce(&DecodeImageData, std::move(callback), codec)); |
| } |
| |
| void DecodeAnimationFile( |
| DecodeAnimationCallback callback, |
| const base::FilePath& file_path, |
| scoped_refptr<base::SequencedTaskRunner> file_task_runner) { |
| ScheduleFileRead(file_path, std::move(file_task_runner), |
| base::BindOnce(&DecodeAnimationData, std::move(callback))); |
| } |
| |
| void DecodeImageData(DecodeImageCallback callback, |
| data_decoder::mojom::ImageCodec codec, |
| const std::string& data) { |
| if (data.empty()) { |
| std::move(callback).Run(gfx::ImageSkia()); |
| return; |
| } |
| data_decoder::DecodeImageIsolated( |
| base::as_byte_span(data), codec, |
| /*shrink_to_fit=*/true, kMaxImageSizeInBytes, |
| /*desired_image_frame_size=*/gfx::Size(), |
| base::BindOnce(&ToImageSkia, std::move(callback))); |
| } |
| |
| void DecodeAnimationData(DecodeAnimationCallback callback, |
| const std::string& data) { |
| if (data.empty()) { |
| std::move(callback).Run(std::vector<AnimationFrame>()); |
| return; |
| } |
| // `shrink_to_fit` is true here so that animations larger than |
| // `kMaxImageSizeInBytes` will have their resolution downscaled instead of |
| // simply failing to decode. |
| data_decoder::DecodeAnimationIsolated( |
| base::as_byte_span(data), /*shrink_to_fit=*/true, kMaxImageSizeInBytes, |
| base::BindOnce(&ToFrames, std::move(callback))); |
| } |
| |
| } // namespace image_util |
| } // namespace ash |