/* * Copyright (c) 2024, Andreas Kling * * SPDX-License-Identifier: BSD-2-Clause */ #include #include #include #include namespace Gfx { struct JPEGLoadingContext { enum class State { NotDecoded, Error, Decoded, }; State state { State::NotDecoded }; RefPtr rgb_bitmap; RefPtr cmyk_bitmap; ReadonlyBytes data; Vector icc_data; JPEGLoadingContext(ReadonlyBytes data) : data(data) { } ErrorOr decode(); }; struct JPEGErrorManager : jpeg_error_mgr { jmp_buf setjmp_buffer {}; }; ErrorOr 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(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(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(cinfo.output_width), static_cast(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(cinfo.output_width), static_cast(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 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> JPEGImageDecoderPlugin::create(ReadonlyBytes data) { return adopt_own(*new JPEGImageDecoderPlugin(make(data))); } ErrorOr JPEGImageDecoderPlugin::frame(size_t index, Optional) { 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 JPEGImageDecoderPlugin::metadata() { return OptionalNone {}; } ErrorOr> 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(*this).frame(0); if (m_context->cmyk_bitmap) return NaturalFrameFormat::CMYK; return NaturalFrameFormat::RGB; } ErrorOr> 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; } }