blob: 1d12f24e318594e12e6456f063dbe2e0fe0b3413 [file] [log] [blame]
// Copyright 2022 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/ui/scenic/lib/image-compression/image_compression.h"
#include <lib/async/cpp/task.h>
#include <lib/async/dispatcher.h>
#include <lib/syslog/cpp/macros.h>
#include <iostream>
// PNG Imports
#include <fuchsia/images/cpp/fidl.h>
#include <lib/async/default.h>
#include <png.h>
#include <src/lib/fostr/fidl/fuchsia/images/formatting.h>
#include "src/lib/fsl/vmo/sized_vmo.h"
#include "src/lib/fsl/vmo/vector.h"
namespace image_compression {
void ImageCompression::EncodePng(EncodePngRequest& request, EncodePngCompleter::Sync& completer) {
// This is an async protocol.
auto async_completer = completer.ToAsync();
// Ensure all required input fields exist.
if (!request.raw_vmo() || !request.image_dimensions() || !request.png_vmo()) {
async_completer.Reply(
fit::as_error(fuchsia::ui::compression::internal::ImageCompressionError::MISSING_ARGS));
return;
}
// Convert in_vmo to a useable format.
uint64_t in_vmo_size;
request.raw_vmo()->get_size(&in_vmo_size);
fsl::SizedVmo raw_image = fsl::SizedVmo(std::move(*request.raw_vmo()), in_vmo_size);
// Convert png_vmo to a useable format.
uint64_t png_vmo_size;
request.png_vmo()->get_size(&png_vmo_size);
// Ensure both vmo sizes are consistent with the client-given width and height.
const uint32_t width = request.image_dimensions()->width();
const uint32_t height = request.image_dimensions()->height();
// We are assuming BGRA_8 format for the input.
const uint32_t pixel_size = 4;
const uint32_t stride = width * pixel_size;
// Do some size checks.
// Check that the stated width and height is compatible with |in_vmo_size|.
if (width * height * pixel_size > in_vmo_size) {
FX_LOGS(WARNING) << "ImageCompression::EncodePng(): in_vmo is too small";
async_completer.Reply(
fit::as_error(fuchsia::ui::compression::internal::ImageCompressionError::INVALID_ARGS));
return;
}
// Check that the png_vmo_size is large enough to hold any potential PNG encoding of |in_vmo|.
if (png_vmo_size < in_vmo_size + zx_system_get_page_size()) {
FX_LOGS(WARNING) << "ImageCompression::EncodePng(): png_vmo is too small";
async_completer.Reply(
fit::as_error(fuchsia::ui::compression::internal::ImageCompressionError::INVALID_ARGS));
return;
}
// Start libpng specific operations.
png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
if (!png_ptr) {
async_completer.Reply(
fit::as_error(fuchsia::ui::compression::internal::ImageCompressionError::BAD_OPERATION));
return;
}
png_infop info_ptr = png_create_info_struct(png_ptr);
if (!info_ptr) {
png_destroy_write_struct(&png_ptr, nullptr);
async_completer.Reply(
fit::as_error(fuchsia::ui::compression::internal::ImageCompressionError::BAD_OPERATION));
return;
}
// This is libpng obscure syntax for setting up the error handler.
if (setjmp(png_jmpbuf(png_ptr))) {
FX_LOGS(WARNING) << "ImageCompression::EncodePng(): Cannot set libpng error handler";
async_completer.Reply(
fit::as_error(fuchsia::ui::compression::internal::ImageCompressionError::BAD_OPERATION));
return;
}
static constexpr int bit_depth = 8;
// Set the headers: output is 8-bit depth, RGBA format.
png_set_IHDR(png_ptr, info_ptr, (uint32_t)width, (uint32_t)height, bit_depth, PNG_COLOR_TYPE_RGBA,
PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
std::vector<uint8_t> imgdata;
if (!fsl::VectorFromVmo(raw_image, &imgdata)) {
FX_LOGS(WARNING) << "ImageCompression::EncodePng(): Cannot extract data from raw image VMO";
async_completer.Reply(
fit::as_error(fuchsia::ui::compression::internal::ImageCompressionError::BAD_OPERATION));
return;
}
// Give libpng a pointer to each pixel at the beginning of each row.
std::vector<uint8_t*> rows(height);
for (size_t y = 0; y < height; ++y) {
rows[y] = imgdata.data() + y * stride;
}
png_set_rows(png_ptr, info_ptr, rows.data());
// Tell libpng how to process each row.
std::vector<uint8_t> pixels;
png_set_write_fn(
png_ptr, &pixels,
[](png_structp png_ptr, png_bytep data, png_size_t length) {
auto p = reinterpret_cast<std::vector<uint8_t>*>(png_get_io_ptr(png_ptr));
p->insert(p->end(), data, data + length);
},
nullptr);
// This is actually the blocking call. At the end, the info and image will be written to |pixels|.
// Note the swizzle flag, which instructs the library to read from BGRA data.
png_write_png(png_ptr, info_ptr, PNG_TRANSFORM_BGR, nullptr);
// This may fail if the client does not allow resizing - but that's okay as it's not necessary.
request.png_vmo()->set_size(pixels.size());
// Success!
request.png_vmo()->write(pixels.data(), 0, pixels.size() * sizeof(uint8_t));
async_completer.Reply(fit::ok());
}
} // namespace image_compression