ladybird/Userland/Libraries/LibGfx/ImageFormats/JPEGLoader.cpp
0x4261756D 6923008a55 JPEGLoader: Fix infinite loop on incomplete data
If the image data to decode is incomplete, e.g. a corrupt image missing
its last scanlines the decoder would previously keep looping for ever.
By breaking out of the loop if no more scanlines were produced we can at
least display the partial image up to that point.
2024-10-12 19:21:03 +02:00

227 lines
7.2 KiB
C++

/*
* Copyright (c) 2024, Andreas Kling <andreas@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibGfx/CMYKBitmap.h>
#include <LibGfx/ImageFormats/JPEGLoader.h>
#include <jpeglib.h>
#include <setjmp.h>
namespace Gfx {
struct JPEGLoadingContext {
enum class State {
NotDecoded,
Error,
Decoded,
};
State state { State::NotDecoded };
RefPtr<Gfx::Bitmap> rgb_bitmap;
RefPtr<Gfx::CMYKBitmap> cmyk_bitmap;
ReadonlyBytes data;
Vector<u8> icc_data;
JPEGLoadingContext(ReadonlyBytes data)
: data(data)
{
}
ErrorOr<void> decode();
};
struct JPEGErrorManager : jpeg_error_mgr {
jmp_buf setjmp_buffer {};
};
ErrorOr<void> JPEGLoadingContext::decode()
{
struct jpeg_decompress_struct cinfo;
struct JPEGErrorManager jerr;
cinfo.err = jpeg_std_error(&jerr);
jpeg_source_mgr source_manager {};
if (setjmp(jerr.setjmp_buffer)) {
jpeg_destroy_decompress(&cinfo);
state = State::Error;
return Error::from_string_literal("Failed to decode JPEG");
}
jerr.error_exit = [](j_common_ptr cinfo) {
char buffer[JMSG_LENGTH_MAX];
(*cinfo->err->format_message)(cinfo, buffer);
dbgln("JPEG error: {}", buffer);
longjmp(static_cast<JPEGErrorManager*>(cinfo->err)->setjmp_buffer, 1);
};
jpeg_create_decompress(&cinfo);
source_manager.next_input_byte = data.data();
source_manager.bytes_in_buffer = data.size();
source_manager.init_source = [](j_decompress_ptr) {};
source_manager.fill_input_buffer = [](j_decompress_ptr) -> boolean { return false; };
source_manager.skip_input_data = [](j_decompress_ptr context, long num_bytes) {
if (num_bytes > static_cast<long>(context->src->bytes_in_buffer)) {
context->src->bytes_in_buffer = 0;
return;
}
context->src->next_input_byte += num_bytes;
context->src->bytes_in_buffer -= num_bytes;
};
source_manager.resync_to_restart = jpeg_resync_to_restart;
source_manager.term_source = [](j_decompress_ptr) {};
cinfo.src = &source_manager;
jpeg_save_markers(&cinfo, JPEG_APP0 + 2, 0xFFFF);
if (jpeg_read_header(&cinfo, TRUE) != JPEG_HEADER_OK) {
jpeg_destroy_decompress(&cinfo);
return Error::from_string_literal("Failed to read JPEG header");
}
if (cinfo.jpeg_color_space == JCS_CMYK || cinfo.jpeg_color_space == JCS_YCCK) {
cinfo.out_color_space = JCS_CMYK;
} else {
cinfo.out_color_space = JCS_EXT_BGRX;
}
jpeg_start_decompress(&cinfo);
bool could_read_all_scanlines = true;
if (cinfo.out_color_space == JCS_EXT_BGRX) {
rgb_bitmap = TRY(Gfx::Bitmap::create(Gfx::BitmapFormat::BGRx8888, { static_cast<int>(cinfo.output_width), static_cast<int>(cinfo.output_height) }));
while (cinfo.output_scanline < cinfo.output_height) {
auto* row_ptr = (u8*)rgb_bitmap->scanline(cinfo.output_scanline);
auto out_size = jpeg_read_scanlines(&cinfo, &row_ptr, 1);
if (cinfo.output_scanline < cinfo.output_height && out_size == 0) {
dbgln("JPEG Warning: Decoding produced no more scanlines in scanline {}/{}.", cinfo.output_scanline, cinfo.output_height);
could_read_all_scanlines = false;
break;
}
}
} else {
cmyk_bitmap = TRY(CMYKBitmap::create_with_size({ static_cast<int>(cinfo.output_width), static_cast<int>(cinfo.output_height) }));
while (cinfo.output_scanline < cinfo.output_height) {
auto* row_ptr = (u8*)cmyk_bitmap->scanline(cinfo.output_scanline);
auto out_size = jpeg_read_scanlines(&cinfo, &row_ptr, 1);
if (cinfo.output_scanline < cinfo.output_height && out_size == 0) {
dbgln("JPEG Warning: Decoding produced no more scanlines in scanline {}/{}.", cinfo.output_scanline, cinfo.output_height);
could_read_all_scanlines = false;
break;
}
}
}
JOCTET* icc_data_ptr = nullptr;
unsigned int icc_data_length = 0;
if (jpeg_read_icc_profile(&cinfo, &icc_data_ptr, &icc_data_length)) {
icc_data.resize(icc_data_length);
memcpy(icc_data.data(), icc_data_ptr, icc_data_length);
free(icc_data_ptr);
}
if (could_read_all_scanlines)
jpeg_finish_decompress(&cinfo);
else
jpeg_abort_decompress(&cinfo);
jpeg_destroy_decompress(&cinfo);
if (cmyk_bitmap && !rgb_bitmap)
rgb_bitmap = TRY(cmyk_bitmap->to_low_quality_rgb());
state = State::Decoded;
return {};
}
JPEGImageDecoderPlugin::JPEGImageDecoderPlugin(NonnullOwnPtr<JPEGLoadingContext> context)
: m_context(move(context))
{
}
JPEGImageDecoderPlugin::~JPEGImageDecoderPlugin() = default;
IntSize JPEGImageDecoderPlugin::size()
{
if (m_context->state == JPEGLoadingContext::State::NotDecoded)
(void)frame(0);
if (m_context->state == JPEGLoadingContext::State::Error)
return {};
if (m_context->rgb_bitmap)
return m_context->rgb_bitmap->size();
if (m_context->cmyk_bitmap)
return m_context->cmyk_bitmap->size();
return {};
}
bool JPEGImageDecoderPlugin::sniff(ReadonlyBytes data)
{
return data.size() > 3
&& data.data()[0] == 0xFF
&& data.data()[1] == 0xD8
&& data.data()[2] == 0xFF;
}
ErrorOr<NonnullOwnPtr<ImageDecoderPlugin>> JPEGImageDecoderPlugin::create(ReadonlyBytes data)
{
return adopt_own(*new JPEGImageDecoderPlugin(make<JPEGLoadingContext>(data)));
}
ErrorOr<ImageFrameDescriptor> JPEGImageDecoderPlugin::frame(size_t index, Optional<IntSize>)
{
if (index > 0)
return Error::from_string_literal("JPEGImageDecoderPlugin: Invalid frame index");
if (m_context->state == JPEGLoadingContext::State::Error)
return Error::from_string_literal("JPEGImageDecoderPlugin: Decoding failed");
if (m_context->state < JPEGLoadingContext::State::Decoded) {
TRY(m_context->decode());
m_context->state = JPEGLoadingContext::State::Decoded;
}
return ImageFrameDescriptor { m_context->rgb_bitmap, 0 };
}
Optional<Metadata const&> JPEGImageDecoderPlugin::metadata()
{
return OptionalNone {};
}
ErrorOr<Optional<ReadonlyBytes>> JPEGImageDecoderPlugin::icc_data()
{
if (m_context->state == JPEGLoadingContext::State::NotDecoded)
(void)frame(0);
if (!m_context->icc_data.is_empty())
return m_context->icc_data;
return OptionalNone {};
}
NaturalFrameFormat JPEGImageDecoderPlugin::natural_frame_format() const
{
if (m_context->state == JPEGLoadingContext::State::NotDecoded)
(void)const_cast<JPEGImageDecoderPlugin&>(*this).frame(0);
if (m_context->cmyk_bitmap)
return NaturalFrameFormat::CMYK;
return NaturalFrameFormat::RGB;
}
ErrorOr<NonnullRefPtr<CMYKBitmap>> JPEGImageDecoderPlugin::cmyk_frame()
{
if (m_context->state == JPEGLoadingContext::State::NotDecoded)
(void)frame(0);
if (m_context->state == JPEGLoadingContext::State::Error)
return Error::from_string_literal("JPEGImageDecoderPlugin: Decoding failed");
if (!m_context->cmyk_bitmap)
return Error::from_string_literal("JPEGImageDecoderPlugin: No CMYK data available");
return *m_context->cmyk_bitmap;
}
}