Update wxWEBPHandler

Add function to get the WebP library version.

Add LoadAnimation function to get all frame images, background colour and duration.

Use WebPAnimDecoder to decode frames other than the default (-1) frame.
This ensures all returned frames will have the same image size.
WebPDemuxGetFrame can return frames smaller than the full image size, but there is no way to get the offset of the frame.

Reduce code duplication when creating rgba image.

Add enum to store image format (undefined/mixed, lossy or lossless).
Add lossless and lossy quality options to wxImage for saving WebP.

Add wxBITMAP_TYPE_WEBP_RESOURCE because all other image handlers have this too.
This commit is contained in:
Maarten Bent
2025-05-10 17:19:26 +02:00
parent d408cea7e4
commit 412cfe96f2
4 changed files with 259 additions and 117 deletions
+1
View File
@@ -80,6 +80,7 @@ enum wxBitmapType
wxBITMAP_TYPE_MACCURSOR,
wxBITMAP_TYPE_MACCURSOR_RESOURCE,
wxBITMAP_TYPE_WEBP,
wxBITMAP_TYPE_WEBP_RESOURCE,
wxBITMAP_TYPE_MAX,
wxBITMAP_TYPE_ANY = 50
+40 -10
View File
@@ -2,38 +2,68 @@
// Name: wx/imagwebp.h
// Purpose: wxImage WebP handler
// Author: Hermann Höhne
// Created: 2024-03-08
// Copyright: (c) Hermann Höhne
// Licence: wxWidgets licence
// Licence: wxWindows licence
/////////////////////////////////////////////////////////////////////////////
#ifndef _WX_IMAGWEBP_H_
#define _WX_IMAGWEBP_H_
#include "wx/defs.h"
#if wxUSE_LIBWEBP
#include "wx/colour.h"
#include "wx/image.h"
#include "wx/versioninfo.h"
//-----------------------------------------------------------------------------
// wxWEBPHandler
//-----------------------------------------------------------------------------
#if wxUSE_LIBWEBP
namespace wxWebPImageFormat
{
enum wxWebPImageFormat : int
{
Undefined = 0,
Lossy = 1,
Lossless = 2
};
}
#define wxIMAGE_OPTION_WEBP_QUALITY wxS("WebPQuality")
#define wxIMAGE_OPTION_WEBP_FORMAT wxS("WebPFormat")
struct wxWebPAnimationFrame
{
wxImage image;
wxColour bgColour;
int duration = 0;
};
class WXDLLIMPEXP_CORE wxWEBPHandler : public wxImageHandler
{
public:
inline wxWEBPHandler()
wxWEBPHandler()
{
m_name = wxT("WebP file");
m_extension = wxT("webp");
m_name = wxS("WebP file");
m_extension = wxS("webp");
m_type = wxBITMAP_TYPE_WEBP;
m_mime = wxT("image/webp");
m_mime = wxS("image/webp");
}
static wxVersionInfo GetLibraryVersionInfo();
#if wxUSE_STREAMS
virtual bool LoadFile(wxImage *image, wxInputStream& stream, bool verbose=true, int index=-1) override;
virtual bool SaveFile(wxImage *image, wxOutputStream& stream, bool verbose=true) override;
virtual bool LoadFile(wxImage* image, wxInputStream& stream, bool verbose = true, int index = -1) override;
virtual bool SaveFile(wxImage* image, wxOutputStream& stream, bool verbose = true) override;
virtual bool LoadAnimation(std::vector<wxWebPAnimationFrame>& frames, wxInputStream& stream, bool verbose = true);
protected:
virtual bool DoCanRead(wxInputStream& stream) override;
// see implementation for why this is disabled:
//virtual int DoGetImageCount(wxInputStream &stream) override;
virtual int DoGetImageCount(wxInputStream& stream) override;
#endif // wxUSE_STREAMS
private:
+216 -107
View File
@@ -2,8 +2,9 @@
// Name: src/common/imagwebp.cpp
// Purpose: wxImage WebP handler
// Author: Hermann Höhne
// Created: 2024-03-08
// Copyright: (c) Hermann Höhne
// Licence: wxWidgets licence
// Licence: wxWindows licence
/////////////////////////////////////////////////////////////////////////////
// based on code by Sylvain Bougnoux, Khral Steelforge and Markus Juergens
@@ -14,16 +15,16 @@
#if wxUSE_IMAGE && wxUSE_LIBWEBP
#ifndef WX_PRECOMP
#include "wx/log.h"
#include "wx/translation.h"
#endif
#include "wx/imagwebp.h"
#include "webp/demux.h"
#include "webp/decode.h"
#include "webp/encode.h"
#include <vector>
#ifndef WX_PRECOMP
#include "wx/intl.h"
#include "wx/log.h"
#endif
//-----------------------------------------------------------------------------
// wxWEBPHandler
@@ -33,199 +34,307 @@ wxIMPLEMENT_DYNAMIC_CLASS(wxWEBPHandler, wxImageHandler);
#if wxUSE_STREAMS
#include <wx/mstream.h>
#include "wx/mstream.h"
#include <memory>
#include <functional>
namespace
{
typedef std::unique_ptr<WebPDemuxer, std::function<void(WebPDemuxer*)>> WebPDemuxerPtr;
typedef std::unique_ptr<WebPAnimDecoder, std::function<void(WebPAnimDecoder*)>> WebPAnimDecoderPtr;
typedef std::unique_ptr<uint8_t, std::function<void(uint8_t*)>> WebPDecodeRGBAPtr;
bool DecodeWebPDataIntoImage(wxImage *image, WebPData *webp_data, bool verbose) {
bool DecodeWebPDataIntoImage(wxImage* image, WebPData* webp_data, bool verbose)
{
WebPBitstreamFeatures features;
VP8StatusCode status = WebPGetFeatures(webp_data->bytes, webp_data->size, &features);
if (status != VP8_STATUS_OK)
{
if (verbose)
{
wxLogError("WebP: GetFeatures not OK.");
wxLogError(_("WebP: GetFeatures not OK."));
}
return false;
}
image->Create(features.width, features.height, false); // this allocates memory
if (!image->Create(features.width, features.height, false))
{
if (verbose)
{
wxLogError(_("WebP: wxImage::Create failed."));
}
return false;
}
image->SetOption(wxIMAGE_OPTION_WEBP_FORMAT, features.format);
if (features.has_alpha)
{
// image has alpha channel. needs to be decoded, then re-ordered.
uint8_t * rgba = WebPDecodeRGBA(webp_data->bytes, webp_data->size, &features.width, &features.height);
if (nullptr == rgba)
WebPDecodeRGBAPtr rgba(
WebPDecodeRGBA(webp_data->bytes, webp_data->size, &features.width, &features.height),
WebPFree
);
if (rgba == nullptr)
{
if (verbose)
{
wxLogError("WebP: WebPDecodeRGBA failed.");
wxLogError(_("WebP: WebPDecodeRGBA failed."));
}
return false;
}
image->InitAlpha();
unsigned char * rgb = image->GetData();
unsigned char * alpha = image->GetAlpha();
size_t rgba_index = 0, rgb_index = 0, alpha_index = 0;
for (int pixel_counter = 0; pixel_counter < image->GetWidth() * image->GetHeight(); pixel_counter++)
{
rgb[rgb_index++] = rgba[rgba_index++]; // R
rgb[rgb_index++] = rgba[rgba_index++]; // G
rgb[rgb_index++] = rgba[rgba_index++]; // B
alpha[alpha_index++] = rgba[rgba_index++]; // A
}
WebPFree(rgba);
image->SetDataRGBA(rgba.get());
}
else
{
// image has no alpha channel. decode into target buffer directly.
int buffer_size = image->GetWidth() * image->GetHeight() * 3;
size_t buffer_size = (size_t)image->GetWidth() * (size_t)image->GetHeight() * 3;
int stride = image->GetWidth() * 3;
uint8_t * output_buffer = WebPDecodeRGBInto(webp_data->bytes, webp_data->size, image->GetData(), buffer_size, stride);
if (nullptr == output_buffer)
uint8_t* output_buffer = WebPDecodeRGBInto(webp_data->bytes, webp_data->size, image->GetData(), buffer_size, stride);
if (output_buffer == nullptr)
{
if (verbose)
{
wxLogError("WebP: WebPDecodeRGBInto failed.");
wxLogError(_("WebP: WebPDecodeRGBInto failed."));
}
return false;
}
}
image->SetMask(false); // all examples do this, so I do so as well
return true;
}
bool DecodeWebPFrameIntoImage(wxImage *image, int index, WebPDemuxerPtr & demuxer, bool verbose)
WebPDemuxerPtr CreateDemuxer(wxMemoryOutputStream& mos, bool verbose = false)
{
bool ok = false;
// apparently, index can be -1 for "don't care", but libwebp does care
if (index < 0)
wxStreamBuffer* mosb = mos.GetOutputStreamBuffer();
if (mosb == nullptr)
{
index = 0;
}
WebPIterator iter;
// wxImageHandler index starts from 0 (first frame)
// WebPDemuxGetFrame to starts from 1 (0 means "last frame")
if (WebPDemuxGetFrame(demuxer.get(), index+1, &iter))
{
ok = DecodeWebPDataIntoImage(image, &iter.fragment, verbose);
WebPDemuxReleaseIterator(&iter);
}
return ok;
}
WebPDemuxerPtr CreateDemuxer(wxInputStream& stream, bool verbose = false) {
wxMemoryOutputStream * mos = new wxMemoryOutputStream;
stream.Read(*mos); // this reads the entire file into memory
// TODO: Only read data as needed. WebPDemux can operate on partial data.
// Could save some bandwidth with e.g. DoGetImageCount
wxStreamBuffer * mosb = mos->GetOutputStreamBuffer();
WebPData * webp_data = new WebPData;
webp_data->bytes = reinterpret_cast<uint8_t *>(mosb->GetBufferStart());
webp_data->size = mosb->GetBufferSize();
WebPDemuxerPtr demux
(
WebPDemux(webp_data),
[mos, webp_data](WebPDemuxer * demux)
{
// delete the demuxer
WebPDemuxDelete(demux);
// delete the buffers after the WebPDemuxer is deleted
delete webp_data;
delete mos;
}
);
if (demux == nullptr)
{
// creating the demuxer failed, but the buffers still exist
// and need to be cleaned up
delete webp_data;
delete mos;
if (verbose)
{
wxLogError("WebP: WebPDemux failed.");
wxLogError(_("WebP: CreateDemuxer wxStreamBuffer failed."));
}
return nullptr;
}
WebPData webp_data{};
webp_data.bytes = reinterpret_cast<uint8_t*>(mosb->GetBufferStart());
webp_data.size = mosb->GetBufferSize();
WebPDemuxerPtr demux(
WebPDemux(&webp_data),
WebPDemuxDelete
);
if (demux == nullptr)
{
if (verbose)
{
wxLogError(_("WebP: CreateDemuxer WebPDemux failed."));
}
}
return demux;
}
bool wxWEBPHandler::LoadFile(wxImage *image, wxInputStream& stream, bool verbose, int index)
} // namespace
bool wxWEBPHandler::LoadFile(wxImage* image, wxInputStream& stream, bool verbose, int index)
{
image->Destroy(); // all examples do this, so I do so as well
if (image == nullptr)
return false;
bool ok = false;
WebPDemuxerPtr demux = CreateDemuxer(stream, verbose);
if (nullptr != demux)
image->Destroy();
bool useFrameDemuxer = (index == -1);
if (index == -1)
index = 0;
if (useFrameDemuxer)
{
ok = DecodeWebPFrameIntoImage(image, index, demux, verbose);
// Default first frame, Use RGB(A) decoder
//
// We can't use this for other frame indices, because the frame might
// be a sub-frame, smaller than the full image. Its size is known, but
// the x_offset and y_offset can not be queried, so we can't
// reconstruct the full-size image.
wxMemoryOutputStream mos;
stream.Read(mos);
WebPDemuxerPtr demux = CreateDemuxer(mos, verbose);
if (demux != nullptr)
{
WebPIterator iter;
if (WebPDemuxGetFrame(demux.get(), index + 1, &iter))
{
ok = DecodeWebPDataIntoImage(image, &iter.fragment, verbose);
WebPDemuxReleaseIterator(&iter);
}
}
}
else if (index >= 0)
{
// Use animation decoder, always RGBA
std::vector<wxWebPAnimationFrame> frames;
LoadAnimation(frames, stream, verbose);
size_t idx = static_cast<size_t>(index);
if (idx < frames.size())
{
*image = frames.at(idx).image.Copy();
ok = true;
}
}
if (!ok && image->IsOk())
image->Destroy();
return ok;
}
bool wxWEBPHandler::SaveFile(wxImage *image, wxOutputStream& stream, bool)
bool wxWEBPHandler::LoadAnimation(std::vector<wxWebPAnimationFrame>& frames, wxInputStream& stream, bool verbose)
{
float quality_factor = 90; // if you change this, update the documentation, too
if (image->HasOption(wxIMAGE_OPTION_QUALITY))
wxMemoryOutputStream mos;
stream.Read(mos);
wxStreamBuffer* mosb = mos.GetOutputStreamBuffer();
if (mosb == nullptr)
{
quality_factor = image->GetOptionInt(wxIMAGE_OPTION_QUALITY);
if (verbose)
{
wxLogError(_("WebP: LoadAnimation wxStreamBuffer failed."));
}
return false;
}
WebPData webp_data{};
webp_data.bytes = reinterpret_cast<uint8_t*>(mosb->GetBufferStart());
webp_data.size = mosb->GetBufferSize();
WebPAnimDecoderOptions dec_options;
WebPAnimDecoderOptionsInit(&dec_options);
WebPAnimDecoderPtr decoder(
WebPAnimDecoderNew(&webp_data, &dec_options),
WebPAnimDecoderDelete
);
if (decoder == nullptr)
{
if (verbose)
{
wxLogError(_("WebP: WebPAnimDecoderNew failed."));
}
return false;
}
WebPAnimInfo anim_info;
WebPAnimDecoderGetInfo(decoder.get(), &anim_info);
int prevTimestamp = 0;
while (WebPAnimDecoderHasMoreFrames(decoder.get()))
{
uint8_t* buf;
int timestamp;
WebPAnimDecoderGetNext(decoder.get(), &buf, &timestamp);
if (buf == nullptr)
{
if (verbose)
{
wxLogError(_("WebP: WebPAnimDecoderGetNext failed."));
}
break;
}
wxWebPAnimationFrame frame;
frame.image.Create(anim_info.canvas_width, anim_info.canvas_height, false);
frame.image.SetDataRGBA(buf);
frame.bgColour.SetRGBA(anim_info.bgcolor);
frame.duration = timestamp - prevTimestamp;
prevTimestamp = timestamp;
frames.push_back(std::move(frame));
}
WebPAnimDecoderReset(decoder.get());
return true;
}
bool wxWEBPHandler::SaveFile(wxImage* image, wxOutputStream& stream, bool)
{
if (image == nullptr)
return false;
float quality_factor = 90; // if you change this, update the documentation, too
if (image->HasOption(wxIMAGE_OPTION_WEBP_QUALITY))
quality_factor = image->GetOptionInt(wxIMAGE_OPTION_WEBP_QUALITY);
bool lossless = image->GetOptionInt(wxIMAGE_OPTION_WEBP_FORMAT) == wxWebPImageFormat::Lossless;
size_t output_size = 0;
uint8_t * output = nullptr;
unsigned char * rgb = image->GetData();
uint8_t* output = nullptr;
unsigned char* rgb = image->GetData();
if (image->HasAlpha())
{
unsigned char * alpha = image->GetAlpha();
int stride = image->GetWidth() * 4; // stride is the "width" of a "line" in bytes
std::vector<unsigned char> rgba(stride * image->GetHeight());
unsigned char* alpha = image->GetAlpha();
size_t pixel_count = (size_t)image->GetWidth() * (size_t)image->GetHeight();
std::vector<unsigned char> rgba(pixel_count * 4);
size_t rgba_index = 0, rgb_index = 0, alpha_index = 0;
for (int pixel_counter = 0; pixel_counter < image->GetWidth() * image->GetHeight(); pixel_counter++)
for (size_t pixel_counter = 0; pixel_counter < pixel_count; pixel_counter++)
{
rgba[rgba_index++] = rgb[rgb_index++]; // R
rgba[rgba_index++] = rgb[rgb_index++]; // G
rgba[rgba_index++] = rgb[rgb_index++]; // B
rgba[rgba_index++] = alpha[alpha_index++]; // A
}
output_size = WebPEncodeRGBA(rgba.data(), image->GetWidth(), image->GetHeight(), stride, quality_factor, &output);
int stride = image->GetWidth() * 4; // stride is the "width" of a "line" in bytes
if (lossless)
output_size = WebPEncodeLosslessRGBA(rgba.data(), image->GetWidth(), image->GetHeight(), stride, &output);
else
output_size = WebPEncodeRGBA(rgba.data(), image->GetWidth(), image->GetHeight(), stride, quality_factor, &output);
}
else
{
int stride = image->GetWidth() * 3; // stride is the "width" of a "line" in bytes
output_size = WebPEncodeRGB(rgb, image->GetWidth(), image->GetHeight(), stride, quality_factor, &output);
if (lossless)
output_size = WebPEncodeLosslessRGB(rgb, image->GetWidth(), image->GetHeight(), stride, &output);
else
output_size = WebPEncodeRGB(rgb, image->GetWidth(), image->GetHeight(), stride, quality_factor, &output);
}
stream.WriteAll(output, output_size);
WebPFree(output);
return (output_size > 0 && stream.LastWrite() == output_size);
}
/*
TODO: Write tests for animations, then enable this method (remember the header, too).
int wxWEBPHandler::DoGetImageCount(wxInputStream & stream) {
int frame_count = -1;
WebPDemuxerPtr demux = CreateDemuxer(stream);
if (nullptr != demux)
int wxWEBPHandler::DoGetImageCount(wxInputStream& stream)
{
int frame_count = 0;
wxMemoryOutputStream mos;
stream.Read(mos);
WebPDemuxerPtr demux = CreateDemuxer(mos);
if (demux != nullptr)
{
frame_count = WebPDemuxGetI(demux.get(), WEBP_FF_FRAME_COUNT);
}
return frame_count;
}
*/
bool wxWEBPHandler::DoCanRead(wxInputStream& stream)
{
// check header according to https://developers.google.com/speed/webp/docs/riff_container
const std::string riff = "RIFF";
const std::string webp = "WEBP";
const int buffer_size = 12;
char buffer[buffer_size];
// it's ok to modify the stream position here
stream.Read(buffer, buffer_size);
if (stream.LastRead() != buffer_size)
{
return false;
}
return std::string(buffer, 4) == riff && std::string(&buffer[8], 4) == webp;
return memcmp(buffer, "RIFF", 4) == 0 && memcmp(buffer + 8, "WEBP", 4) == 0;
}
#endif // wxUSE_STREAMS
/*static*/ wxVersionInfo wxWEBPHandler::GetLibraryVersionInfo()
{
int webpVersion = WebPGetDecoderVersion();
return wxVersionInfo("libwebp",
webpVersion >> 16 & 0xff,
webpVersion >> 8 & 0xff,
webpVersion & 0xff);
}
#endif // wxUSE_IMAGE && wxUSE_LIBWEBP
+2
View File
@@ -485,6 +485,8 @@ inline const char* wxQtConvertBitmapType(wxBitmapType type)
case wxBITMAP_TYPE_TGA:
case wxBITMAP_TYPE_MACCURSOR:
case wxBITMAP_TYPE_MACCURSOR_RESOURCE:
case wxBITMAP_TYPE_WEBP:
case wxBITMAP_TYPE_WEBP_RESOURCE:
case wxBITMAP_TYPE_MAX:
case wxBITMAP_TYPE_ANY:
default: