Add support for working with PNG "Description" text chunk

Add wxIMAGE_OPTION_PNG_DESCRIPTION which is filled with the contents of
the (first) "Description" chunk from the PNG file when loading it and
saved into such (iTXt) chunk when saving.

Fixes #25556.

Closes #25565.
This commit is contained in:
GenevensiS
2025-06-29 01:35:38 +02:00
committed by Vadim Zeitlin
parent d3da0e8ea2
commit 4c2ffb0c5b
5 changed files with 96 additions and 1 deletions
+1
View File
@@ -27,6 +27,7 @@
#define wxIMAGE_OPTION_PNG_COMPRESSION_MEM_LEVEL wxT("PngZM")
#define wxIMAGE_OPTION_PNG_COMPRESSION_STRATEGY wxT("PngZS")
#define wxIMAGE_OPTION_PNG_COMPRESSION_BUFFER_SIZE wxT("PngZB")
#define wxIMAGE_OPTION_PNG_DESCRIPTION wxT("PngDescription")
enum
{
+11
View File
@@ -148,6 +148,7 @@ enum wxImagePNGType
#define wxIMAGE_OPTION_PNG_COMPRESSION_MEM_LEVEL wxString("PngZM")
#define wxIMAGE_OPTION_PNG_COMPRESSION_STRATEGY wxString("PngZS")
#define wxIMAGE_OPTION_PNG_COMPRESSION_BUFFER_SIZE wxString("PngZB")
#define wxIMAGE_OPTION_PNG_DESCRIPTION wxString("PngDescription")
#define wxIMAGE_OPTION_TIFF_BITSPERSAMPLE wxString("BitsPerSample")
#define wxIMAGE_OPTION_TIFF_SAMPLESPERPIXEL wxString("SamplesPerPixel")
@@ -1322,6 +1323,16 @@ public:
its own comment. If there is only a comment in the first frame of
a GIF it will not be repeated in other frames.
Options specific to wxPNGHandler:
@li @c wxIMAGE_OPTION_PNG_DESCRIPTION: The contents of this option
will be converted to an uncompressed iTXt chunk with the
key: "Description", and written to the PNG file upon saving.
Contents of tXTt and iTXt chunks with the key: "Description" are
also automatically retrieved upon loading a PNG file, and stored
in this option. If multiple chunks with this key are present,
only one is retrieved.
@since 3.3.1
@param name
The name of the option, case-insensitive.
@return
+1
View File
@@ -12,6 +12,7 @@
#define wxIMAGE_OPTION_PNG_COMPRESSION_MEM_LEVEL wxT("PngZM")
#define wxIMAGE_OPTION_PNG_COMPRESSION_STRATEGY wxT("PngZS")
#define wxIMAGE_OPTION_PNG_COMPRESSION_BUFFER_SIZE wxT("PngZB")
#define wxIMAGE_OPTION_PNG_DESCRIPTION wxT("PngDescription")
/* These are already in interface/wx/image.h
They were likely put there as a stopgap, but they've been there long enough
+55 -1
View File
@@ -37,6 +37,8 @@
#include <unordered_map>
#define wxIMAGE_OPTION_PNG_DESCRIPTION_KEY "Description"
// ----------------------------------------------------------------------------
// local functions
// ----------------------------------------------------------------------------
@@ -189,7 +191,7 @@ PNGLINKAGEMODE wx_PNG_warning(png_structp png_ptr, png_const_charp message)
wxPNGInfoStruct *info = png_ptr ? WX_PNG_INFO(png_ptr) : nullptr;
if ( !info || info->verbose )
{
wxLogWarning( wxString::FromAscii(message) );
wxLogWarning( wxString::FromUTF8(message) );
}
}
@@ -342,6 +344,40 @@ wxPNGImageData::DoLoadPNGFile(wxImage* image, wxPNGInfoStruct& wxinfo)
return;
png_read_image( png_ptr, lines );
// load "Description" text chunk
png_textp text_ptr;
const int num_comments = png_get_text( png_ptr, info_ptr, &text_ptr, nullptr );
for (int i = 0; i < num_comments; ++i)
{
const wxString& key = wxString::From8BitData(text_ptr[i].key);
if (key == wxIMAGE_OPTION_PNG_DESCRIPTION_KEY)
{
wxString description;
switch (text_ptr[i].compression)
{
case PNG_TEXT_COMPRESSION_zTXt:
case PNG_TEXT_COMPRESSION_NONE:
// tEXt chunk: uses Latin-1 encoding.
description = wxString::From8BitData(text_ptr[i].text);
break;
case PNG_ITXT_COMPRESSION_zTXt:
case PNG_ITXT_COMPRESSION_NONE:
// iTXt chunk: uses UTF-8 encoding.
description = wxString::FromUTF8(text_ptr[i].text);
break;
default:
// Invalid type, should we report it? Probably not worth it.
break;
}
if (!description.empty())
image->SetOption(wxIMAGE_OPTION_PNG_DESCRIPTION, description);
}
}
png_read_end( png_ptr, info_ptr );
#if wxUSE_PALETTE
@@ -737,6 +773,24 @@ bool wxPNGHandler::SaveFile( wxImage *image, wxOutputStream& stream, bool verbos
png_set_pHYs( png_ptr, info_ptr, resX, resY, PNG_RESOLUTION_METER );
png_set_sBIT( png_ptr, info_ptr, &sig_bit );
// save "Description" text chunk
if (image->HasOption(wxIMAGE_OPTION_PNG_DESCRIPTION))
{
const wxString& description = image->GetOption(wxIMAGE_OPTION_PNG_DESCRIPTION);
png_text text;
text.key = const_cast<char*>(wxIMAGE_OPTION_PNG_DESCRIPTION_KEY);
text.lang = nullptr;
text.lang_key = nullptr;
text.compression = PNG_ITXT_COMPRESSION_NONE;
const auto& buf = description.utf8_str();
text.text = const_cast<char*>(buf.data());
text.itxt_length = buf.length();
text.text_length = 0;
png_set_text( png_ptr, info_ptr, &text, 1 );
}
png_write_info( png_ptr, info_ptr );
png_set_shift( png_ptr, &sig_bit );
png_set_packing( png_ptr );
+28
View File
@@ -1069,6 +1069,34 @@ TEST_CASE_METHOD(ImageHandlersInit, "wxImage::SavePNG", "[image]")
}
static void TestPNGDescription(const wxString& description)
{
wxImage image("horse.png");
image.SetOption(wxIMAGE_OPTION_PNG_DESCRIPTION, description);
wxMemoryOutputStream memOut;
REQUIRE(image.SaveFile(memOut, wxBITMAP_TYPE_PNG));
wxMemoryInputStream memIn(memOut);
REQUIRE(image.LoadFile(memIn));
CHECK(image.GetOption(wxIMAGE_OPTION_PNG_DESCRIPTION) == description);
}
TEST_CASE_METHOD(ImageHandlersInit, "wxImage::PNGDescription", "[image]")
{
// Test writing a description and reading it back.
TestPNGDescription("Providing the PNG a pneumatic puma as a present");
// Test writing and reading a description again but with non-ASCII characters.
TestPNGDescription("Тестирование 테스트 一 二 三");
// Test writing and reading a description again but with a long description.
TestPNGDescription(wxString(wxT('a'), 256)
+ wxString(wxT('b'), 256)
+ wxString(wxT('c'), 256));
}
#if wxUSE_LIBTIFF
static void TestTIFFImage(const wxString& option, int value,
const wxImage *compareImage = nullptr)