blob: bb7940a6a4bcfa33e9864593700da9110b73bc87 [file] [log] [blame]
// Copyright 2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// -----------------------------------------------------------------------------
//
// JPEG-XL encode.
//
// Author: Vincent Rabaud (vrabaud@google.com)
#include "./image_enc.h"
#include "src/wp2/base.h"
#ifdef HAVE_CONFIG_H
#include "wp2/config.h"
#endif
#ifdef WP2_HAVE_JXL
#include <cmath>
#include <cstddef>
#include <cstdint>
#include "src/utils/utils.h"
#include "src/utils/vector.h"
#include "src/wp2/format_constants.h"
#include "third_party/jpegxl/lib/include/jxl/codestream_header.h"
#include "third_party/jpegxl/lib/include/jxl/color_encoding.h"
#include "third_party/jpegxl/lib/include/jxl/encode.h"
#include "third_party/jpegxl/lib/include/jxl/encode_cxx.h"
#include "third_party/jpegxl/lib/include/jxl/types.h"
#endif
namespace WP2 {
// -----------------------------------------------------------------------------
#if defined(WP2_HAVE_JXL)
namespace {
JxlPixelFormat ArgbBufferToJxlPixelFormat(const WP2::ArgbBuffer& image) {
JxlPixelFormat pixel_format;
const uint32_t bytes_per_channel = (WP2Formatbpc(image.format()) + 7) / 8;
pixel_format.num_channels = WP2FormatBpp(image.format()) / bytes_per_channel;
pixel_format.data_type =
WP2Formatbpc(image.format()) == 8 ? JXL_TYPE_UINT8 : JXL_TYPE_UINT16;
// TODO(yguyon): Fix endianness TODO in libwebp2/public/src/wp2/decode.h
pixel_format.endianness = JXL_NATIVE_ENDIAN;
pixel_format.align = image.stride();
return pixel_format;
}
size_t ArgbBufferSize(const WP2::ArgbBuffer& image) {
return static_cast<size_t>(image.height() - 1) * image.stride() +
static_cast<size_t>(image.width()) * WP2FormatBpp(image.format());
}
} // namespace
WP2Status CompressJXL(const ArgbBuffer& buffer_in, float quality, int effort,
Writer* const output) {
ArgbBuffer buffer(buffer_in.HasTransparency() ? WP2_RGBA_32 : WP2_RGB_24);
WP2_CHECK_STATUS(buffer.ConvertFrom(buffer_in));
WP2_CHECK_OK(buffer.format() == WP2_RGBA_32 || buffer.format() == WP2_RGB_24,
WP2_STATUS_INVALID_CONFIGURATION);
const JxlEncoderPtr encoder = JxlEncoderMake(nullptr);
WP2_CHECK_ALLOC_OK(encoder != nullptr);
JxlBasicInfo basic_info;
JxlEncoderInitBasicInfo(&basic_info);
basic_info.xsize = buffer.width();
basic_info.ysize = buffer.height();
basic_info.bits_per_sample = WP2Formatbpc(buffer.format());
basic_info.uses_original_profile = JXL_TRUE; // mandatory for lossless
basic_info.num_color_channels = 3;
if (WP2FormatHasAlpha(buffer.format())) {
basic_info.num_extra_channels = 1;
basic_info.alpha_bits = basic_info.bits_per_sample;
basic_info.alpha_premultiplied = WP2IsPremultiplied(buffer.format());
// JxlEncoderSetExtraChannelInfo() does not need to be called for alpha
// apparently.
}
JxlEncoderStatus status = JxlEncoderSetBasicInfo(encoder.get(), &basic_info);
WP2_CHECK_OK(status == JXL_ENC_SUCCESS, WP2_STATUS_INVALID_CONFIGURATION);
JxlColorEncoding color_encoding = {};
JxlColorEncodingSetToSRGB(&color_encoding, /*is_gray=*/JXL_FALSE);
status = JxlEncoderSetColorEncoding(encoder.get(), &color_encoding);
WP2_CHECK_OK(status == JXL_ENC_SUCCESS, WP2_STATUS_INVALID_CONFIGURATION);
JxlEncoderFrameSettings* frame_settings =
JxlEncoderFrameSettingsCreate(encoder.get(), nullptr);
WP2_CHECK_ALLOC_OK(frame_settings != nullptr);
if (quality == kMaxQuality) {
status = JxlEncoderSetFrameLossless(frame_settings, JXL_TRUE);
WP2_CHECK_OK(status == JXL_ENC_SUCCESS, WP2_STATUS_INVALID_CONFIGURATION);
// JXL_ENC_FRAME_SETTING_KEEP_INVISIBLE should be ON by default if lossless.
} else {
const float distance = JxlEncoderDistanceFromQuality(quality);
status = JxlEncoderSetFrameDistance(frame_settings, distance);
WP2_CHECK_OK(status == JXL_ENC_SUCCESS, WP2_STATUS_INVALID_CONFIGURATION);
}
// Allow for the maximum effort.
const int scaled_effort = std::round(1 + 10. * effort / kMaxEffort);
JxlEncoderAllowExpertOptions(encoder.get());
status = JxlEncoderFrameSettingsSetOption(
frame_settings, JXL_ENC_FRAME_SETTING_EFFORT, scaled_effort);
WP2_CHECK_OK(status == JXL_ENC_SUCCESS, WP2_STATUS_INVALID_CONFIGURATION);
const JxlPixelFormat pixel_format = ArgbBufferToJxlPixelFormat(buffer);
status = JxlEncoderAddImageFrame(frame_settings, &pixel_format,
buffer.GetRow(0), ArgbBufferSize(buffer));
WP2_CHECK_OK(status == JXL_ENC_SUCCESS, WP2_STATUS_INVALID_CONFIGURATION);
JxlEncoderCloseInput(encoder.get());
WP2::Vector<uint8_t> vec;
WP2_CHECK_ALLOC_OK(vec.resize(64));
uint8_t* next_out = vec.data();
size_t avail_out = vec.size() - (next_out - vec.data());
do {
status = JxlEncoderProcessOutput(encoder.get(), &next_out, &avail_out);
if (status == JXL_ENC_NEED_MORE_OUTPUT) {
size_t offset = next_out - vec.data();
WP2_CHECK_ALLOC_OK(vec.resize(vec.size() * 2));
next_out = vec.data() + offset;
avail_out = vec.size() - offset;
}
} while (status == JXL_ENC_NEED_MORE_OUTPUT);
WP2_CHECK_ALLOC_OK(output->Append(vec.data(), next_out - vec.data()));
WP2_CHECK_OK(status == JXL_ENC_SUCCESS, WP2_STATUS_INVALID_CONFIGURATION);
return WP2_STATUS_OK;
}
#else
WP2Status CompressJXL(const ArgbBuffer&, float, int, Writer*) {
return WP2_STATUS_UNSUPPORTED_FEATURE;
}
#endif // defined(WP2_HAVE_JXL)
// -----------------------------------------------------------------------------
} // namespace WP2