diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:06:44 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:06:44 +0000 |
commit | ed5640d8b587fbcfed7dd7967f3de04b37a76f26 (patch) | |
tree | 7a5f7c6c9d02226d7471cb3cc8fbbf631b415303 /vcl/source/filter/webp | |
parent | Initial commit. (diff) | |
download | libreoffice-ed5640d8b587fbcfed7dd7967f3de04b37a76f26.tar.xz libreoffice-ed5640d8b587fbcfed7dd7967f3de04b37a76f26.zip |
Adding upstream version 4:7.4.7.upstream/4%7.4.7upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'vcl/source/filter/webp')
-rw-r--r-- | vcl/source/filter/webp/reader.cxx | 318 | ||||
-rw-r--r-- | vcl/source/filter/webp/writer.cxx | 205 |
2 files changed, 523 insertions, 0 deletions
diff --git a/vcl/source/filter/webp/reader.cxx b/vcl/source/filter/webp/reader.cxx new file mode 100644 index 000000000..3c0b1399c --- /dev/null +++ b/vcl/source/filter/webp/reader.cxx @@ -0,0 +1,318 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you 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 http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <memory> +#include <vcl/graph.hxx> +#include <tools/stream.hxx> +#include <filter/WebpReader.hxx> +#include <bitmap/BitmapWriteAccess.hxx> +#include <salinst.hxx> +#include <sal/log.hxx> +#include <unotools/configmgr.hxx> +#include <svdata.hxx> +#include <comphelper/scopeguard.hxx> + +#include <webp/decode.h> + +static bool readWebpInfo(SvStream& stream, std::vector<uint8_t>& data, + WebPBitstreamFeatures& features) +{ + for (;;) + { + // Read 4096 (more) bytes. + size_t lastSize = data.size(); + data.resize(data.size() + 4096); + sal_Size nBytesRead = stream.ReadBytes(data.data() + lastSize, 4096); + if (nBytesRead <= 0) + return false; + data.resize(lastSize + nBytesRead); + int status = WebPGetFeatures(data.data(), data.size(), &features); + if (status == VP8_STATUS_OK) + break; + if (status == VP8_STATUS_NOT_ENOUGH_DATA) + continue; // Try again with 4096 more bytes read. + return false; + } + return true; +} + +static bool readWebp(SvStream& stream, Graphic& graphic) +{ + WebPDecoderConfig config; + if (!WebPInitDecoderConfig(&config)) + { + SAL_WARN("vcl.filter.webp", "WebPInitDecoderConfig() failed"); + return false; + } + comphelper::ScopeGuard freeBuffer([&config]() { WebPFreeDecBuffer(&config.output); }); + std::vector<uint8_t> data; + if (!readWebpInfo(stream, data, config.input)) + return false; + // Here various parts of 'config' can be altered if wanted. + const int& width = config.input.width; + const int& height = config.input.height; + const int& has_alpha = config.input.has_alpha; + + if (width > SAL_MAX_INT32 / 8 || height > SAL_MAX_INT32 / 8) + return false; // avoid overflows later + + const bool bFuzzing = utl::ConfigManager::IsFuzzing(); + const bool bSupportsBitmap32 = bFuzzing || ImplGetSVData()->mpDefInst->supportsBitmap32(); + + Bitmap bitmap; + AlphaMask bitmapAlpha; + if (bSupportsBitmap32 && has_alpha) + { + bitmap = Bitmap(Size(width, height), vcl::PixelFormat::N32_BPP); + } + else + { + bitmap = Bitmap(Size(width, height), vcl::PixelFormat::N24_BPP); + if (has_alpha) + bitmapAlpha = AlphaMask(Size(width, height)); + } + + BitmapScopedWriteAccess access(bitmap); + if (!access) + return false; + // If data cannot be read directly into the bitmap, read data first to this buffer and then convert. + std::vector<uint8_t> tmpRgbaData; + enum class PixelMode + { + DirectRead, // read data directly to the bitmap + Split, // read to tmp buffer and split to rgb and alpha + SetPixel // read to tmp buffer and use setPixel() + }; + PixelMode pixelMode = PixelMode::SetPixel; + + config.output.width = width; + config.output.height = height; + config.output.is_external_memory = 1; + if (bSupportsBitmap32 && has_alpha) + { + switch (RemoveScanline(access->GetScanlineFormat())) + { + // Our bitmap32 code expects premultiplied. + case ScanlineFormat::N32BitTcRgba: + config.output.colorspace = MODE_rgbA; + pixelMode = PixelMode::DirectRead; + break; + case ScanlineFormat::N32BitTcBgra: + config.output.colorspace = MODE_bgrA; + pixelMode = PixelMode::DirectRead; + break; + case ScanlineFormat::N32BitTcArgb: + config.output.colorspace = MODE_Argb; + pixelMode = PixelMode::DirectRead; + break; + default: + config.output.colorspace = MODE_RGBA; + pixelMode = PixelMode::SetPixel; + break; + } + } + else + { + if (has_alpha) + { + switch (RemoveScanline(access->GetScanlineFormat())) + { + case ScanlineFormat::N24BitTcRgb: + config.output.colorspace = MODE_RGBA; + pixelMode = PixelMode::Split; + break; + case ScanlineFormat::N24BitTcBgr: + config.output.colorspace = MODE_BGRA; + pixelMode = PixelMode::Split; + break; + default: + config.output.colorspace = MODE_RGBA; + pixelMode = PixelMode::SetPixel; + break; + } + } + else + { + switch (RemoveScanline(access->GetScanlineFormat())) + { + case ScanlineFormat::N24BitTcRgb: + config.output.colorspace = MODE_RGB; + pixelMode = PixelMode::DirectRead; + break; + case ScanlineFormat::N24BitTcBgr: + config.output.colorspace = MODE_BGR; + pixelMode = PixelMode::DirectRead; + break; + default: + config.output.colorspace = MODE_RGBA; + pixelMode = PixelMode::SetPixel; + break; + } + } + } + if (pixelMode == PixelMode::DirectRead) + { + config.output.u.RGBA.rgba = access->GetBuffer(); + config.output.u.RGBA.stride = access->GetScanlineSize(); + config.output.u.RGBA.size = access->GetScanlineSize() * access->Height(); + } + else + { + tmpRgbaData.resize(width * height * 4); + config.output.u.RGBA.rgba = tmpRgbaData.data(); + config.output.u.RGBA.stride = width * 4; + config.output.u.RGBA.size = tmpRgbaData.size(); + } + + std::unique_ptr<WebPIDecoder, decltype(&WebPIDelete)> decoder(WebPIDecode(nullptr, 0, &config), + WebPIDelete); + + bool success = true; + for (;;) + { + // During first iteration, use data read while reading the header. + int status = WebPIAppend(decoder.get(), data.data(), data.size()); + if (status == VP8_STATUS_OK) + break; + if (status != VP8_STATUS_SUSPENDED) + { + // An error, still try to return at least a partially read bitmap, + // even if returning an error flag. + success = false; + break; + } + // If more data is needed, reading 4096 bytes more and repeat. + data.resize(4096); + sal_Size nBytesRead = stream.ReadBytes(data.data(), 4096); + if (nBytesRead <= 0) + { + // Truncated file, again try to return at least something. + success = false; + break; + } + data.resize(nBytesRead); + } + + switch (pixelMode) + { + case PixelMode::DirectRead: + { + // Adjust for IsBottomUp() if necessary. + if (access->IsBottomUp()) + { + std::vector<char> tmp; + const sal_uInt32 lineSize = access->GetScanlineSize(); + tmp.resize(lineSize); + for (tools::Long y = 0; y < access->Height() / 2; ++y) + { + tools::Long otherY = access->Height() - 1 - y; + memcpy(tmp.data(), access->GetScanline(y), lineSize); + memcpy(access->GetScanline(y), access->GetScanline(otherY), lineSize); + memcpy(access->GetScanline(otherY), tmp.data(), lineSize); + } + } + break; + } + case PixelMode::Split: + { + // Split to normal and alpha bitmaps. + AlphaScopedWriteAccess accessAlpha(bitmapAlpha); + for (tools::Long y = 0; y < access->Height(); ++y) + { + const unsigned char* src = tmpRgbaData.data() + width * 4 * y; + unsigned char* dstB = access->GetScanline(y); + unsigned char* dstA = accessAlpha->GetScanline(y); + for (tools::Long x = 0; x < access->Width(); ++x) + { + memcpy(dstB, src, 3); + *dstA = 255 - *(src + 3); + src += 4; + dstB += 3; + dstA += 1; + } + } + break; + } + case PixelMode::SetPixel: + { + for (tools::Long y = 0; y < access->Height(); ++y) + { + const unsigned char* src = tmpRgbaData.data() + width * 4 * y; + for (tools::Long x = 0; x < access->Width(); ++x) + { + sal_uInt8 r = src[0]; + sal_uInt8 g = src[1]; + sal_uInt8 b = src[2]; + sal_uInt8 a = src[3]; + access->SetPixel(y, x, Color(ColorAlpha, a, r, g, b)); + src += 4; + } + } + if (!bitmapAlpha.IsEmpty()) + { + AlphaScopedWriteAccess accessAlpha(bitmapAlpha); + for (tools::Long y = 0; y < accessAlpha->Height(); ++y) + { + const unsigned char* src = tmpRgbaData.data() + width * 4 * y; + for (tools::Long x = 0; x < accessAlpha->Width(); ++x) + { + sal_uInt8 a = src[3]; + accessAlpha->SetPixelIndex(y, x, 255 - a); + src += 4; + } + } + } + break; + } + } + + access.reset(); // Flush BitmapScopedWriteAccess. + if (bSupportsBitmap32 && has_alpha) + graphic = BitmapEx(bitmap); + else + { + if (has_alpha) + graphic = BitmapEx(bitmap, bitmapAlpha); + else + graphic = BitmapEx(bitmap); + } + return success; +} + +bool ImportWebpGraphic(SvStream& rStream, Graphic& rGraphic) +{ + bool bRetValue = readWebp(rStream, rGraphic); + if (!bRetValue) + rStream.SetError(SVSTREAM_FILEFORMAT_ERROR); + return bRetValue; +} + +bool ReadWebpInfo(SvStream& stream, Size& pixelSize, sal_uInt16& bitsPerPixel, bool& hasAlpha) +{ + std::vector<uint8_t> data; + WebPBitstreamFeatures features; + if (!readWebpInfo(stream, data, features)) + return false; + pixelSize = Size(features.width, features.height); + bitsPerPixel = features.has_alpha ? 32 : 24; + hasAlpha = features.has_alpha; + return true; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/filter/webp/writer.cxx b/vcl/source/filter/webp/writer.cxx new file mode 100644 index 000000000..f29dad007 --- /dev/null +++ b/vcl/source/filter/webp/writer.cxx @@ -0,0 +1,205 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you 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 http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <vcl/graph.hxx> +#include <tools/stream.hxx> +#include <filter/WebpWriter.hxx> +#include <vcl/BitmapReadAccess.hxx> +#include <comphelper/scopeguard.hxx> +#include <sal/log.hxx> +#include <o3tl/string_view.hxx> + +#include <webp/encode.h> + +static int writerFunction(const uint8_t* data, size_t size, const WebPPicture* picture) +{ + SvStream* stream = static_cast<SvStream*>(picture->custom_ptr); + return stream->WriteBytes(data, size) == size ? 1 : 0; +} + +static WebPPreset presetToValue(std::u16string_view preset) +{ + if (o3tl::equalsIgnoreAsciiCase(preset, u"picture")) + return WEBP_PRESET_PICTURE; + if (o3tl::equalsIgnoreAsciiCase(preset, u"photo")) + return WEBP_PRESET_PHOTO; + if (o3tl::equalsIgnoreAsciiCase(preset, u"drawing")) + return WEBP_PRESET_DRAWING; + if (o3tl::equalsIgnoreAsciiCase(preset, u"icon")) + return WEBP_PRESET_ICON; + if (o3tl::equalsIgnoreAsciiCase(preset, u"text")) + return WEBP_PRESET_TEXT; + return WEBP_PRESET_DEFAULT; +} + +static bool writeWebp(SvStream& rStream, const BitmapEx& bitmapEx, bool lossless, + std::u16string_view preset, int quality) +{ + WebPConfig config; + if (!WebPConfigInit(&config)) + { + SAL_WARN("vcl.filter.webp", "WebPConfigInit() failed"); + return false; + } + if (lossless) + { + if (!WebPConfigLosslessPreset(&config, 6)) + { + SAL_WARN("vcl.filter.webp", "WebPConfigLosslessPreset() failed"); + return false; + } + } + else + { + if (!WebPConfigPreset(&config, presetToValue(preset), quality)) + { + SAL_WARN("vcl.filter.webp", "WebPConfigPreset() failed"); + return false; + } + } + // Here various parts of 'config' can be altered if wanted. + assert(WebPValidateConfig(&config)); + + const int width = bitmapEx.GetSizePixel().Width(); + const int height = bitmapEx.GetSizePixel().Height(); + + WebPPicture picture; + if (!WebPPictureInit(&picture)) + { + SAL_WARN("vcl.filter.webp", "WebPPictureInit() failed"); + return false; + } + picture.width = width; + picture.height = height; + picture.use_argb = lossless ? 1 : 0; // libwebp recommends argb only for lossless + comphelper::ScopeGuard freePicture([&picture]() { WebPPictureFree(&picture); }); + + // Apparently libwebp needs the entire image data at once in WebPPicture, + // so allocate it and copy there. + Bitmap bitmap(bitmapEx.GetBitmap()); + AlphaMask bitmapAlpha; + if (bitmapEx.IsAlpha()) + bitmapAlpha = bitmapEx.GetAlpha(); + Bitmap::ScopedReadAccess access(bitmap); + AlphaMask::ScopedReadAccess accessAlpha(bitmapAlpha); + bool dataDone = false; + if (!access->IsBottomUp() && bitmapAlpha.IsEmpty()) + { + // Try to directly copy the bitmap data. + switch (access->GetScanlineFormat()) + { + case ScanlineFormat::N24BitTcRgb: + if (!WebPPictureImportRGB(&picture, access->GetBuffer(), access->GetScanlineSize())) + { + SAL_WARN("vcl.filter.webp", "WebPPictureImportRGB() failed"); + return false; + } + dataDone = true; + break; + case ScanlineFormat::N24BitTcBgr: + if (!WebPPictureImportBGR(&picture, access->GetBuffer(), access->GetScanlineSize())) + { + SAL_WARN("vcl.filter.webp", "WebPPictureImportBGR() failed"); + return false; + } + dataDone = true; + break; + // Our argb formats are premultiplied, so can't read directly using libwebp functions. + default: + break; + } + } + if (!dataDone) + { + // It would be simpler to convert the bitmap to 32bpp, but our 32bpp support is broken + // (it's unspecified whether it's premultiplied or not, for example). So handle this manually. + // Special handling for some common cases, generic otherwise. + if (!WebPPictureAlloc(&picture)) + { + SAL_WARN("vcl.filter.webp", "WebPPictureAlloc() failed"); + return false; + } + std::vector<uint8_t> data; + const int bpp = 4; + data.resize(width * height * bpp); + if (!bitmapAlpha.IsEmpty()) + { + for (tools::Long y = 0; y < access->Height(); ++y) + { + unsigned char* dst = data.data() + width * bpp * y; + const sal_uInt8* srcB = access->GetScanline(y); + const sal_uInt8* srcA = accessAlpha->GetScanline(y); + for (tools::Long x = 0; x < access->Width(); ++x) + { + BitmapColor color = access->GetPixelFromData(srcB, x); + BitmapColor transparency = accessAlpha->GetPixelFromData(srcA, x); + color.SetAlpha(255 - transparency.GetIndex()); + dst[0] = color.GetRed(); + dst[1] = color.GetGreen(); + dst[2] = color.GetBlue(); + dst[3] = color.GetAlpha(); + dst += bpp; + } + } + } + else + { + for (tools::Long y = 0; y < access->Height(); ++y) + { + unsigned char* dst = data.data() + width * bpp * y; + const sal_uInt8* src = access->GetScanline(y); + for (tools::Long x = 0; x < access->Width(); ++x) + { + Color color = access->GetPixelFromData(src, x); + dst[0] = color.GetRed(); + dst[1] = color.GetGreen(); + dst[2] = color.GetBlue(); + dst[3] = color.GetAlpha(); + dst += bpp; + } + } + } + // And now import from the temporary data. Use the import function rather + // than writing the data directly to avoid the need to write the data + // in the exact format WebPPicture wants (YUV, etc.). + if (!WebPPictureImportRGBA(&picture, data.data(), width * bpp)) + { + SAL_WARN("vcl.filter.webp", "WebPPictureImportRGBA() failed"); + return false; + } + } + + picture.writer = writerFunction; + picture.custom_ptr = &rStream; + return WebPEncode(&config, &picture); +} + +bool ExportWebpGraphic(SvStream& rStream, const Graphic& rGraphic, + FilterConfigItem* pFilterConfigItem) +{ + BitmapEx bitmap = rGraphic.GetBitmapEx(); + // If lossless, neither presets nor quality matter. + bool lossless = pFilterConfigItem->ReadBool("Lossless", true); + // Preset is WebPPreset values. + const OUString preset = pFilterConfigItem->ReadString("Preset", ""); + int quality = pFilterConfigItem->ReadInt32("Quality", 75); + return writeWebp(rStream, bitmap, lossless, preset, quality); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |