头文件
class PDFCorePngManager
{
public:
enum class PDFCoreInterpolationMethod {
NEAREST_NEIGHBOR,
BILINEAR,
BICUBIC
};
public:
PDFCorePngManager() = default;
~PDFCorePngManager() = default;
void ExtractPngDataFromDIBitmap(CFX_DIBitmap* sourceBitmap,
std::vector<uint8_t>& png_data, int compressionLevel = 9);
void ExtractSmaskDataFromDIBitmap(CFX_DIBitmap* sourceBitmap,
std::vector<uint8_t>& smask_image_data);
void ExtractRgbDataFromDIBitmap(CFX_DIBitmap* sourceBitmap,
std::vector<uint8_t>& rgb_image_data);
void ExtractRgbSmaskDataFromDIBitmap(CFX_DIBitmap* sourceBitmap,
std::vector<uint8_t>& rgb_image_data,
std::vector<uint8_t>& smask_image_data);
bool WriteVectorToFile(const std::vector<uint8_t>& data, const std::string& filename);
std::vector<uint8_t> ResizePngImage(
CFX_DIBitmap* sourceBitmap,
PDFCoreInterpolationMethod interpolation_method = PDFCoreInterpolationMethod::NEAREST_NEIGHBOR,
int WidthScaleFactor = 5
);
#if !defined(__ANDROID__) && !(defined(__APPLE__) && TARGET_OS_IPHONE)
bool PNGQuantizerCompress(std::vector<uint8_t>& input_data, std::vector<uint8_t>& output_data);
#endif
bool ExtractImageDataFromDIBitmap(CFX_DIBitmap* sourceBitmap, std::vector<uint8_t>& image_pixel_data, uint32_t& width, uint32_t& height,uint8_t& bitDepth);
bool ExtractRGBDataFromDIBitmap(CFX_DIBitmap* sourceBitmap, std::vector<uint8_t>& image_pixel_data, uint32_t &width, uint32_t &height, uint8_t &bitDepth);
bool ResizeImageToPixel(
CFX_DIBitmap* sourceBitmap,
PDFCoreInterpolationMethod pdfore_interpolation_method,
int WidthScaleFactor,
uint32_t& outWidth,
uint32_t& outHeight,
uint8_t& outBitDepth,
int& outFormat,
std::vector<uint8_t>& outPixels,
bool isJpegFormat,
bool use_multithread
);
private:
PDFCorePngManager(const PDFCorePngManager&) = delete;
PDFCorePngManager& operator=(const PDFCorePngManager&) = delete;
};
#endif
实现文件
class DIBitmapConverter {
public:
struct ImageData {
std::vector<uint8_t> rgbData;
std::vector<uint8_t> smaskData;
unsigned int width = 0;
unsigned int height = 0;
bool hasAlpha = false;
};
static ImageData GetImageData(CFX_DIBitmap* bitmap, bool includeAlpha = true) {
ImageData result;
result.width = bitmap->GetWidth();
result.height = bitmap->GetHeight();
int format = bitmap->GetFormat();
result.hasAlpha = (includeAlpha && HasAlphaChannel(format));
size_t rgbSize = result.width * result.height * (result.hasAlpha ? 4 : 3);
size_t smaskSize = result.width * result.height;
result.rgbData.resize(rgbSize);
result.smaskData.resize(smaskSize);
uint8_t* bitmapData = static_cast<uint8_t*>(bitmap->GetBuffer());
int stride = bitmap->GetPitch();
int bpp = GetBPPFromFormat(format);
#if 0
for (unsigned int y = 0; y < result.height; ++y) {
for (unsigned int x = 0; x < result.width; ++x) {
size_t srcOffset = y * stride + x * (bpp / 8);
size_t rgbOffset = (y * result.width + x) * (result.hasAlpha ? 4 : 3);
size_t smaskOffset = y * result.width + x;
ExtractPixelData(bitmapData, srcOffset, format,
result.rgbData, rgbOffset,
result.smaskData, smaskOffset,
result.hasAlpha);
}
}
#else
const int bytesPerPixel = bpp / 8;
const bool hasAlpha = result.hasAlpha;
const int rgbStep = hasAlpha ? 4 : 3;
const size_t width = result.width;
uint8_t* srcData = bitmapData;
uint8_t* rgbDataPtr = result.rgbData.data();
uint8_t* smaskDataPtr = result.smaskData.data();
for (unsigned int y = 0; y < result.height; ++y) {
uint8_t* rowSrc = srcData + y * stride;
uint8_t* rowRgb = rgbDataPtr + y * width * rgbStep;
uint8_t* rowSmask = smaskDataPtr + y * width;
for (unsigned int x = 0; x < width; ++x) {
ExtractPixelData(rowSrc, format,
rowRgb, rowSmask,
hasAlpha);
rowSrc += bytesPerPixel;
rowRgb += rgbStep;
rowSmask++;
}
}
#endif
return result;
}
static bool SetImageData(CFX_DIBitmap* bitmap, const ImageData& imageData) {
unsigned int width = bitmap->GetWidth();
unsigned int height = bitmap->GetHeight();
int format = bitmap->GetFormat();
int bpp = GetBPPFromFormat(format);
if (width != imageData.width || height != imageData.height) {
return false;
}
uint8_t* bitmapData = static_cast<uint8_t*>(bitmap->GetBuffer());
int stride = bitmap->GetPitch();
for (unsigned int y = 0; y < height; ++y) {
for (unsigned int x = 0; x < width; ++x) {
size_t dstOffset = y * stride + x * (bpp / 8);
size_t rgbOffset = (y * width + x) * (imageData.hasAlpha ? 4 : 3);
size_t smaskOffset = y * width + x;
SetPixelData(bitmapData, dstOffset, format,
imageData.rgbData, rgbOffset,
imageData.smaskData, smaskOffset,
imageData.hasAlpha);
}
}
}
static std::unique_ptr<CFX_DIBitmap> CreateBitmapFromData(
const ImageData& imageData
,
FXDIB_Format format = FXDIB_Rgba)
{
auto bitmap = std::make_unique<CFX_DIBitmap>();
if (!bitmap->Create(imageData.width, imageData.height, format, nullptr, 0)) {
#if HasDebugImage
std::cerr << "Failed to create bitmap" << std::endl;
#endif
}
SetImageData(bitmap.get(), imageData);
return bitmap;
}
private:
static int GetBPPFromFormat(int format) {
switch (format) {
case FXDIB_1bppMask: return 1;
case FXDIB_8bppMask:
case FXDIB_8bppRgb:
case FXDIB_8bppRgba:
case FXDIB_8bppCmyk:
case FXDIB_8bppCmyka: return 8;
case FXDIB_Rgb: return 24;
case FXDIB_Rgba:
case FXDIB_Rgb32:
case FXDIB_Argb:
case FXDIB_Cmyk:
case FXDIB_Cmyka: return 32;
default: return 0;
}
}
static bool HasAlphaChannel(int format) {
switch (format) {
case FXDIB_8bppRgba:
case FXDIB_Rgba:
case FXDIB_Rgb32:
case FXDIB_Argb:
case FXDIB_8bppCmyka:
case FXDIB_Cmyka: return true;
default: return false;
}
}
#if 0
static bool ExtractPixelData(
uint8_t* bitmapData, size_t srcOffset, int format,
std::vector<uint8_t>& rgbData, size_t rgbOffset,
std::vector<uint8_t>& smaskData, size_t smaskOffset,
bool includeAlpha)
{
switch (format) {
case FXDIB_8bppMask:
case FXDIB_8bppRgb:
case FXDIB_8bppRgba:
case FXDIB_8bppCmyk:
case FXDIB_8bppCmyka: {
uint8_t gray = bitmapData[srcOffset];
rgbData[rgbOffset] = rgbData[rgbOffset + 1] = rgbData[rgbOffset + 2] = gray;
if (includeAlpha && rgbData.size() > rgbOffset + 3) {
rgbData[rgbOffset + 3] = 255;
}
smaskData[smaskOffset] = 255;
break;
}
case FXDIB_Rgb: {
rgbData[rgbOffset] = bitmapData[srcOffset + 2];
rgbData[rgbOffset + 1] = bitmapData[srcOffset + 1];
rgbData[rgbOffset + 2] = bitmapData[srcOffset];
if (includeAlpha && rgbData.size() > rgbOffset + 3) {
rgbData[rgbOffset + 3] = 255;
}
smaskData[smaskOffset] = 255;
break;
}
case FXDIB_Rgba:
case FXDIB_Rgb32:
case FXDIB_Argb: {
rgbData[rgbOffset] = bitmapData[srcOffset + 2];
rgbData[rgbOffset + 1] = bitmapData[srcOffset + 1];
rgbData[rgbOffset + 2] = bitmapData[srcOffset];
if (includeAlpha) {
if (rgbData.size() > rgbOffset + 3) {
rgbData[rgbOffset + 3] = bitmapData[srcOffset + 3];
}
smaskData[smaskOffset] = bitmapData[srcOffset + 3];
}
else {
smaskData[smaskOffset] = 255;
}
break;
}
default:
#if HasDebugImage
std::cerr << "Unsupported bitmap format" << std::endl;
#endif
return false;
}
}
#else
static void ExtractPixelData(
uint8_t* srcPtr, int format,
uint8_t* rgbPtr, uint8_t* smaskPtr,
bool hasAlpha)
{
switch (format) {
case FXDIB_8bppMask:
case FXDIB_8bppRgb:
case FXDIB_8bppRgba:
case FXDIB_8bppCmyk:
case FXDIB_8bppCmyka: {
uint8_t gray = *srcPtr;
rgbPtr[0] = gray;
rgbPtr[1] = gray;
rgbPtr[2] = gray;
if (hasAlpha) {
rgbPtr[3] = 0xFF;
}
*smaskPtr = 0xFF;
break;
}
case FXDIB_Rgb: {
rgbPtr[0] = srcPtr[2];
rgbPtr[1] = srcPtr[1];
rgbPtr[2] = srcPtr[0];
if (hasAlpha) {
rgbPtr[3] = 0xFF;
}
*smaskPtr = 0xFF;
break;
}
case FXDIB_Rgba:
case FXDIB_Rgb32:
case FXDIB_Argb: {
rgbPtr[0] = srcPtr[2];
rgbPtr[1] = srcPtr[1];
rgbPtr[2] = srcPtr[0];
if (hasAlpha) {
uint8_t alpha = srcPtr[3];
rgbPtr[3] = alpha;
*smaskPtr = alpha;
}
else {
*smaskPtr = 0xFF;
}
break;
}
default: {
rgbPtr[0] = 0;
rgbPtr[1] = 0;
rgbPtr[2] = 0;
if (hasAlpha) {
rgbPtr[3] = 0xFF;
}
*smaskPtr = 0xFF;
break;
}
}
}
#endif
static bool SetPixelData(
uint8_t* bitmapData, size_t dstOffset, int format,
const std::vector<uint8_t>& rgbData, size_t rgbOffset,
const std::vector<uint8_t>& smaskData, size_t smaskOffset,
bool hasAlpha)
{
switch (format) {
case FXDIB_8bppMask:
case FXDIB_8bppRgb:
case FXDIB_8bppRgba:
case FXDIB_8bppCmyk:
case FXDIB_8bppCmyka: {
float gray = 0.299f * rgbData[rgbOffset] +
0.587f * rgbData[rgbOffset + 1] +
0.114f * rgbData[rgbOffset + 2];
bitmapData[dstOffset] = static_cast<uint8_t>(gray);
break;
}
case FXDIB_Rgb: {
bitmapData[dstOffset] = rgbData[rgbOffset + 2];
bitmapData[dstOffset + 1] = rgbData[rgbOffset + 1];
bitmapData[dstOffset + 2] = rgbData[rgbOffset];
break;
}
case FXDIB_Rgba:
case FXDIB_Rgb32:
case FXDIB_Argb: {
bitmapData[dstOffset] = rgbData[rgbOffset + 2];
bitmapData[dstOffset + 1] = rgbData[rgbOffset + 1];
bitmapData[dstOffset + 2] = rgbData[rgbOffset];
if (hasAlpha && rgbData.size() > rgbOffset + 3) {
bitmapData[dstOffset + 3] = smaskData[smaskOffset];
}
else {
bitmapData[dstOffset + 3] = 255;
}
break;
}
default:
#if HasDebugImage
std::cerr << "Unsupported bitmap format" << std::endl;
#endif
return false;
}
}
};
#include <vector>
#include <cstdint>
#include <cmath>
#include <algorithm>
class ImageInterpolator {
public:
enum class Method {
NEAREST_NEIGHBOR,
BILINEAR,
BICUBIC
};
struct ImageData {
std::vector<uint8_t> pixels;
uint32_t width = 0;
uint32_t height = 0;
uint8_t bitDepth = 8;
uint8_t channels = 4;
};
static bool resize(ImageData& image,
uint32_t newWidth,
uint32_t newHeight,
Method method = Method::BILINEAR) {
if (image.pixels.empty() || image.width == 0 || image.height == 0) {
return false;
}
if (newWidth == 0 || newHeight == 0) {
return false;
}
if (newWidth == image.width && newHeight == image.height) {
return true;
}
if (image.bitDepth != 8) {
return false;
}
if (image.channels != 3 && image.channels != 4) {
return false;
}
std::vector<uint8_t> resizedPixels(newWidth * newHeight * image.channels);
for (uint32_t y = 0; y < newHeight; ++y) {
for (uint32_t x = 0; x < newWidth; ++x) {
float srcX = (x + 0.5f) * image.width / newWidth - 0.5f;
float srcY = (y + 0.5f) * image.height / newHeight - 0.5f;
srcX = (std::max)(0.0f, (std::min)(srcX, static_cast<float>(image.width - 1)));
srcY = (std::max)(0.0f, (std::min)(srcY, static_cast<float>(image.height - 1)));
for (int c = 0; c < image.channels; ++c) {
float value = 0.0f;
switch (method) {
case Method::NEAREST_NEIGHBOR:
value = nearestNeighbor(srcX, srcY, c, image);
break;
case Method::BILINEAR:
value = bilinearInterpolation(srcX, srcY, c, image);
break;
case Method::BICUBIC:
value = bicubicInterpolation(srcX, srcY, c, image);
break;
}
resizedPixels[(y * newWidth + x) * image.channels + c] =
static_cast<uint8_t>((std::max)(0.0f, (std::min)(255.0f, value)));
}
}
}
image.width = newWidth;
image.height = newHeight;
image.pixels = std::move(resizedPixels);
return true;
}
static ImageData resizeCopy(const ImageData& source,
uint32_t newWidth,
uint32_t newHeight,
Method method = Method::BILINEAR) {
ImageData result;
result.width = newWidth;
result.height = newHeight;
result.bitDepth = source.bitDepth;
result.channels = source.channels;
if (source.pixels.empty() || source.width == 0 || source.height == 0) {
return result;
}
if (newWidth == 0 || newHeight == 0) {
return result;
}
if (newWidth == source.width && newHeight == source.height) {
result.pixels = source.pixels;
return result;
}
if (source.bitDepth != 8) {
return result;
}
if (source.channels != 3 && source.channels != 4) {
return result;
}
result.pixels.resize(newWidth * newHeight * source.channels);
for (uint32_t y = 0; y < newHeight; ++y) {
for (uint32_t x = 0; x < newWidth; ++x) {
float srcX = (x + 0.5f) * source.width / newWidth - 0.5f;
float srcY = (y + 0.5f) * source.height / newHeight - 0.5f;
srcX = (std::max)(0.0f, (std::min)(srcX, static_cast<float>(source.width - 1)));
srcY = (std::max)(0.0f, (std::min)(srcY, static_cast<float>(source.height - 1)));
for (int c = 0; c < source.channels; ++c) {
float value = 0.0f;
switch (method) {
case Method::NEAREST_NEIGHBOR:
value = nearestNeighbor(srcX, srcY, c, source);
break;
case Method::BILINEAR:
value = bilinearInterpolation(srcX, srcY, c, source);
break;
case Method::BICUBIC:
value = bicubicInterpolation(srcX, srcY, c, source);
break;
}
result.pixels[(y * newWidth + x) * source.channels + c] =
static_cast<uint8_t>((std::max)(0.0f, (std::min)(255.0f, value)));
}
}
}
return result;
}
static bool resizeMulityThread(ImageData& image,
uint32_t newWidth,
uint32_t newHeight,
Method method = Method::BILINEAR,
unsigned int numThreads = std::thread::hardware_concurrency()) {
if (image.pixels.empty() || image.width == 0 || image.height == 0) return false;
if (newWidth == 0 || newHeight == 0) return false;
if (newWidth == image.width && newHeight == image.height) return true;
if (image.bitDepth != 8) return false;
if (image.channels != 3 && image.channels != 4) return false;
std::vector<uint8_t> resizedPixels(newWidth * newHeight * image.channels);
numThreads = (std::min)(numThreads, static_cast<unsigned int>(newHeight));
if (numThreads == 0) numThreads = 1;
auto worker = [&](uint32_t startY, uint32_t endY) {
for (uint32_t y = startY; y < endY; ++y) {
for (uint32_t x = 0; x < newWidth; ++x) {
float srcX = (x + 0.5f) * image.width / newWidth - 0.5f;
float srcY = (y + 0.5f) * image.height / newHeight - 0.5f;
srcX = (std::max)(0.0f, (std::min)(srcX, static_cast<float>(image.width - 1)));
srcY = (std::max)(0.0f, (std::min)(srcY, static_cast<float>(image.height - 1)));
for (int c = 0; c < image.channels; ++c) {
float value = 0.0f;
switch (method) {
case Method::NEAREST_NEIGHBOR:
value = nearestNeighbor(srcX, srcY, c, image);
break;
case Method::BILINEAR:
value = bilinearInterpolation(srcX, srcY, c, image);
break;
case Method::BICUBIC:
value = bicubicInterpolation(srcX, srcY, c, image);
break;
}
resizedPixels[(y * newWidth + x) * image.channels + c] =
static_cast<uint8_t>((std::max)(0.0f, (std::min)(255.0f, value)));
}
}
}
};
std::vector<std::thread> threads;
uint32_t rowsPerThread = newHeight / numThreads;
uint32_t extraRows = newHeight % numThreads;
uint32_t startY = 0;
for (unsigned int i = 0; i < numThreads; ++i) {
uint32_t endY = startY + rowsPerThread + (i < extraRows ? 1 : 0);
threads.emplace_back(worker, startY, endY);
startY = endY;
}
for (auto& thread : threads) {
thread.join();
}
image.width = newWidth;
image.height = newHeight;
image.pixels = std::move(resizedPixels);
return true;
}
static ImageData resizeCopyMulityThread(const ImageData& source,
uint32_t newWidth,
uint32_t newHeight,
Method method = Method::BILINEAR,
unsigned int numThreads = std::thread::hardware_concurrency()) {
ImageData result;
result.width = newWidth;
result.height = newHeight;
result.bitDepth = source.bitDepth;
result.channels = source.channels;
if (source.pixels.empty() || source.width == 0 || source.height == 0) return result;
if (newWidth == 0 || newHeight == 0) return result;
if (newWidth == source.width && newHeight == source.height) {
result.pixels = source.pixels;
return result;
}
if (source.bitDepth != 8) return result;
if (source.channels != 3 && source.channels != 4) return result;
result.pixels.resize(newWidth * newHeight * source.channels);
numThreads = (std::min)(numThreads, static_cast<unsigned int>(newHeight));
if (numThreads == 0) numThreads = 1;
auto worker = [&](uint32_t startY, uint32_t endY) {
for (uint32_t y = startY; y < endY; ++y) {
for (uint32_t x = 0; x < newWidth; ++x) {
float srcX = (x + 0.5f) * source.width / newWidth - 0.5f;
float srcY = (y + 0.5f) * source.height / newHeight - 0.5f;
srcX = (std::max)(0.0f, (std::min)(srcX, static_cast<float>(source.width - 1)));
srcY = (std::max)(0.0f, (std::min)(srcY, static_cast<float>(source.height - 1)));
for (int c = 0; c < source.channels; ++c) {
float value = 0.0f;
switch (method) {
case Method::NEAREST_NEIGHBOR:
value = nearestNeighbor(srcX, srcY, c, source);
break;
case Method::BILINEAR:
value = bilinearInterpolation(srcX, srcY, c, source);
break;
case Method::BICUBIC:
value = bicubicInterpolation(srcX, srcY, c, source);
break;
}
result.pixels[(y * newWidth + x) * source.channels + c] =
static_cast<uint8_t>((std::max)(0.0f, (std::min)(255.0f, value)));
}
}
}
};
std::vector<std::thread> threads;
uint32_t rowsPerThread = newHeight / numThreads;
uint32_t extraRows = newHeight % numThreads;
uint32_t startY = 0;
for (unsigned int i = 0; i < numThreads; ++i) {
uint32_t endY = startY + rowsPerThread + (i < extraRows ? 1 : 0);
threads.emplace_back(worker, startY, endY);
startY = endY;
}
for (auto& thread : threads) {
thread.join();
}
return result;
}
private:
static float nearestNeighbor(float srcX, float srcY, int channel, const ImageData& image) {
int nearestX = static_cast<int>(std::round(srcX));
int nearestY = static_cast<int>(std::round(srcY));
nearestX = clamp(nearestX, 0, image.width - 1);
nearestY = clamp(nearestY, 0, image.height - 1);
return image.pixels[(nearestY * image.width + nearestX) * image.channels + channel];
}
static float bilinearInterpolation(float srcX, float srcY, int channel, const ImageData& image) {
int x1 = static_cast<int>(srcX);
int y1 = static_cast<int>(srcY);
int x2 = (std::min)(x1 + 1, static_cast<int>(image.width - 1));
int y2 = (std::min)(y1 + 1, static_cast<int>(image.height - 1));
float dx = srcX - x1;
float dy = srcY - y1;
float v11 = image.pixels[(y1 * image.width + x1) * image.channels + channel];
float v21 = image.pixels[(y1 * image.width + x2) * image.channels + channel];
float v12 = image.pixels[(y2 * image.width + x1) * image.channels + channel];
float v22 = image.pixels[(y2 * image.width + x2) * image.channels + channel];
return v11 * (1 - dx) * (1 - dy) +
v21 * dx * (1 - dy) +
v12 * (1 - dx) * dy +
v22 * dx * dy;
}
static float bicubicInterpolation(float srcX, float srcY, int channel, const ImageData& image) {
int x0 = static_cast<int>(std::floor(srcX)) - 1;
int y0 = static_cast<int>(std::floor(srcY)) - 1;
float sum = 0.0f;
float sumWeights = 0.0f;
for (int m = 0; m < 4; ++m) {
for (int n = 0; n < 4; ++n) {
int px = clamp(x0 + n, 0, image.width - 1);
int py = clamp(y0 + m, 0, image.height - 1);
float wx = bicubicWeight(srcX - px);
float wy = bicubicWeight(srcY - py);
float weight = wx * wy;
sum += image.pixels[(py * image.width + px) * image.channels + channel] * weight;
sumWeights += weight;
}
}
return sumWeights > 0.0f ? sum / sumWeights : 0.0f;
}
static int clamp(int value, int min, int max) {
return (std::max)(min, (std::min)(value, max));
}
static float bicubicWeight(float x, float a = -0.5f) {
x = std::abs(x);
if (x < 1.0f) {
return (a + 2.0f) * x * x * x - (a + 3.0f) * x * x + 1.0f;
}
else if (x < 2.0f) {
return a * x * x * x - 5.0f * a * x * x + 8.0f * a * x - 4.0f * a;
}
return 0.0f;
}
};
#include <vector>
#include <cstdint>
#include <functional>
class PngManager {
public:
enum class ErrorCode {
Success = 0,
CreateReadStructFailed,
CreateInfoStructFailed,
CreateWriteStructFailed,
PngProcessingError,
UnsupportedFormat,
InvalidParameters,
MemoryAllocationFailed
};
enum class PixelFormat {
Grayscale = PNG_COLOR_TYPE_GRAY,
RGB = PNG_COLOR_TYPE_RGB,
RGBA = PNG_COLOR_TYPE_RGBA,
GrayscaleAlpha = PNG_COLOR_TYPE_GRAY_ALPHA,
Palette = PNG_COLOR_TYPE_PALETTE
};
struct ImageData {
std::vector<uint8_t> pixels;
uint32_t width = 0;
uint32_t height = 0;
PixelFormat format = PixelFormat::RGBA;
uint8_t bitDepth = 8;
};
struct SeparatedChannels {
std::vector<uint8_t> rgbData;
std::vector<uint8_t> alphaData;
uint32_t width = 0;
uint32_t height = 0;
};
PngManager() = default;
~PngManager() = default;
ErrorCode ReadFromMemory(const uint8_t* buffer, size_t size, ImageData& outImage) {
if (!buffer || size == 0) {
return ErrorCode::InvalidParameters;
}
png_structp pngPtr = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
if (!pngPtr) {
return ErrorCode::CreateReadStructFailed;
}
png_infop infoPtr = png_create_info_struct(pngPtr);
if (!infoPtr) {
png_destroy_read_struct(&pngPtr, nullptr, nullptr);
return ErrorCode::CreateInfoStructFailed;
}
if (setjmp(png_jmpbuf(pngPtr))) {
png_destroy_read_struct(&pngPtr, &infoPtr, nullptr);
return ErrorCode::PngProcessingError;
}
struct ReadContext {
const uint8_t* data;
size_t pos;
} ctx{ buffer, 0 };
png_set_read_fn(pngPtr, &ctx, [](png_structp pngPtr, png_bytep data, png_size_t length) {
auto* ctx = static_cast<ReadContext*>(png_get_io_ptr(pngPtr));
memcpy(data, ctx->data + ctx->pos, length);
ctx->pos += length;
});
png_read_info(pngPtr, infoPtr);
outImage.width = png_get_image_width(pngPtr, infoPtr);
outImage.height = png_get_image_height(pngPtr, infoPtr);
outImage.format = static_cast<PixelFormat>(png_get_color_type(pngPtr, infoPtr));
outImage.bitDepth = png_get_bit_depth(pngPtr, infoPtr);
if (outImage.bitDepth == 16) {
png_set_strip_16(pngPtr);
outImage.bitDepth = 8;
}
if (outImage.format == PixelFormat::Palette) {
png_set_palette_to_rgb(pngPtr);
}
if (outImage.format == PixelFormat::Grayscale && outImage.bitDepth < 8) {
png_set_expand_gray_1_2_4_to_8(pngPtr);
}
if (png_get_valid(pngPtr, infoPtr, PNG_INFO_tRNS)) {
png_set_tRNS_to_alpha(pngPtr);
}
if (outImage.format == PixelFormat::RGB ||
outImage.format == PixelFormat::Grayscale ||
outImage.format == PixelFormat::Palette) {
png_set_add_alpha(pngPtr, 0xFF, PNG_FILLER_AFTER);
}
png_read_update_info(pngPtr, infoPtr);
outImage.format = static_cast<PixelFormat>(png_get_color_type(pngPtr, infoPtr));
outImage.bitDepth = png_get_bit_depth(pngPtr, infoPtr);
png_size_t rowBytes = png_get_rowbytes(pngPtr, infoPtr);
outImage.pixels.resize(rowBytes * outImage.height);
std::vector<png_bytep> rowPointers(outImage.height);
for (uint32_t y = 0; y < outImage.height; ++y) {
rowPointers[y] = outImage.pixels.data() + y * rowBytes;
}
png_read_image(pngPtr, rowPointers.data());
png_read_end(pngPtr, nullptr);
png_destroy_read_struct(&pngPtr, &infoPtr, nullptr);
return ErrorCode::Success;
}
ErrorCode WriteToMemory(const ImageData& image, std::vector<uint8_t>& outBuffer, int compressionLevel = 6) {
if (image.pixels.empty() || image.width == 0 || image.height == 0) {
return ErrorCode::InvalidParameters;
}
png_structp pngPtr = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
if (!pngPtr) {
return ErrorCode::CreateWriteStructFailed;
}
png_infop infoPtr = png_create_info_struct(pngPtr);
if (!infoPtr) {
png_destroy_write_struct(&pngPtr, nullptr);
return ErrorCode::CreateInfoStructFailed;
}
if (setjmp(png_jmpbuf(pngPtr))) {
png_destroy_write_struct(&pngPtr, &infoPtr);
return ErrorCode::PngProcessingError;
}
struct WriteContext {
std::vector<uint8_t>* buffer;
} ctx{ &outBuffer };
png_set_write_fn(pngPtr, &ctx, [](png_structp pngPtr, png_bytep data, png_size_t length) {
auto* ctx = static_cast<WriteContext*>(png_get_io_ptr(pngPtr));
size_t oldSize = ctx->buffer->size();
ctx->buffer->resize(oldSize + length);
memcpy(ctx->buffer->data() + oldSize, data, length);
}, nullptr);
png_set_compression_level(pngPtr, compressionLevel);
png_set_IHDR(pngPtr, infoPtr, image.width, image.height,
image.bitDepth, static_cast<int>(image.format),
PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT,
PNG_FILTER_TYPE_DEFAULT);
png_size_t rowBytes = png_get_rowbytes(pngPtr, infoPtr);
std::vector<png_bytep> rowPointers(image.height);
for (uint32_t y = 0; y < image.height; ++y) {
rowPointers[y] = const_cast<uint8_t*>(image.pixels.data()) + y * rowBytes;
}
png_write_info(pngPtr, infoPtr);
png_write_image(pngPtr, rowPointers.data());
png_write_end(pngPtr, nullptr);
png_destroy_write_struct(&pngPtr, &infoPtr);
return ErrorCode::Success;
}
ErrorCode SeparateChannels(const ImageData& image, SeparatedChannels& outChannels) {
if (image.pixels.empty() || image.width == 0 || image.height == 0) {
return ErrorCode::InvalidParameters;
}
outChannels.width = image.width;
outChannels.height = image.height;
switch (image.format) {
case PixelFormat::RGBA: {
size_t pixelCount = image.width * image.height;
outChannels.rgbData.resize(pixelCount * 3);
outChannels.alphaData.resize(pixelCount);
for (size_t i = 0; i < pixelCount; ++i) {
outChannels.rgbData[i * 3] = image.pixels[i * 4];
outChannels.rgbData[i * 3 + 1] = image.pixels[i * 4 + 1];
outChannels.rgbData[i * 3 + 2] = image.pixels[i * 4 + 2];
outChannels.alphaData[i] = image.pixels[i * 4 + 3];
}
break;
}
case PixelFormat::GrayscaleAlpha: {
size_t pixelCount = image.width * image.height;
outChannels.rgbData.resize(pixelCount * 3);
outChannels.alphaData.resize(pixelCount);
for (size_t i = 0; i < pixelCount; ++i) {
uint8_t gray = image.pixels[i * 2];
outChannels.rgbData[i * 3] = gray;
outChannels.rgbData[i * 3 + 1] = gray;
outChannels.rgbData[i * 3 + 2] = gray;
outChannels.alphaData[i] = image.pixels[i * 2 + 1];
}
break;
}
case PixelFormat::RGB:
case PixelFormat::Grayscale: {
size_t pixelCount = image.width * image.height;
if (image.format == PixelFormat::Grayscale) {
outChannels.rgbData.resize(pixelCount * 3);
for (size_t i = 0; i < pixelCount; ++i) {
uint8_t gray = image.pixels[i];
outChannels.rgbData[i * 3] = gray;
outChannels.rgbData[i * 3 + 1] = gray;
outChannels.rgbData[i * 3 + 2] = gray;
}
}
else {
outChannels.rgbData = image.pixels;
}
outChannels.alphaData.clear();
break;
}
default:
return ErrorCode::UnsupportedFormat;
}
return ErrorCode::Success;
}
ErrorCode MergeChannels(const SeparatedChannels& channels, ImageData& outImage, PixelFormat format = PixelFormat::RGBA) {
if (channels.rgbData.empty() || channels.width == 0 || channels.height == 0) {
return ErrorCode::InvalidParameters;
}
outImage.width = channels.width;
outImage.height = channels.height;
outImage.format = format;
outImage.bitDepth = 8;
size_t pixelCount = channels.width * channels.height;
bool hasAlpha = !channels.alphaData.empty();
if (channels.rgbData.size() != pixelCount * 3) {
return ErrorCode::InvalidParameters;
}
if (hasAlpha && channels.alphaData.size() != pixelCount) {
return ErrorCode::InvalidParameters;
}
switch (format) {
case PixelFormat::RGBA: {
outImage.pixels.resize(pixelCount * 4);
for (size_t i = 0; i < pixelCount; ++i) {
outImage.pixels[i * 4] = channels.rgbData[i * 3];
outImage.pixels[i * 4 + 1] = channels.rgbData[i * 3 + 1];
outImage.pixels[i * 4 + 2] = channels.rgbData[i * 3 + 2];
outImage.pixels[i * 4 + 3] = hasAlpha ? channels.alphaData[i] : 0xFF;
}
break;
}
case PixelFormat::GrayscaleAlpha: {
outImage.pixels.resize(pixelCount * 2);
for (size_t i = 0; i < pixelCount; ++i) {
float gray = 0.299f * channels.rgbData[i * 3] +
0.587f * channels.rgbData[i * 3 + 1] +
0.114f * channels.rgbData[i * 3 + 2];
outImage.pixels[i * 2] = static_cast<uint8_t>(gray);
outImage.pixels[i * 2 + 1] = hasAlpha ? channels.alphaData[i] : 0xFF;
}
break;
}
case PixelFormat::RGB: {
outImage.pixels = channels.rgbData;
break;
}
case PixelFormat::Grayscale: {
outImage.pixels.resize(pixelCount);
for (size_t i = 0; i < pixelCount; ++i) {
float gray = 0.299f * channels.rgbData[i * 3] +
0.587f * channels.rgbData[i * 3 + 1] +
0.114f * channels.rgbData[i * 3 + 2];
outImage.pixels[i] = static_cast<uint8_t>(gray);
}
break;
}
default:
return ErrorCode::UnsupportedFormat;
}
return ErrorCode::Success;
}
ErrorCode ExportRgbToMemory(const SeparatedChannels& separated, std::vector<uint8_t>& outBuffer, int compressionLevel = 6) {
if (separated.rgbData.empty() || separated.width == 0 || separated.height == 0) {
return ErrorCode::InvalidParameters;
}
ImageData rgbImage;
rgbImage.width = separated.width;
rgbImage.height = separated.height;
rgbImage.format = PixelFormat::RGB;
rgbImage.bitDepth = 8;
rgbImage.pixels = separated.rgbData;
return WriteToMemory(rgbImage, outBuffer, compressionLevel);
}
ErrorCode ExportSmaskToMemory(const SeparatedChannels& separated, std::vector<uint8_t>& outBuffer, int compressionLevel = 6) {
if (separated.alphaData.empty() || separated.width == 0 || separated.height == 0) {
return ErrorCode::InvalidParameters;
}
ImageData smaskImage;
smaskImage.width = separated.width;
smaskImage.height = separated.height;
smaskImage.format = PixelFormat::Grayscale;
smaskImage.bitDepth = 8;
smaskImage.pixels = separated.alphaData;
return WriteToMemory(smaskImage, outBuffer, compressionLevel);
}
ErrorCode ExportSeparatedChannels(const SeparatedChannels& separated,
std::vector<uint8_t>& outRgbBuffer,
std::vector<uint8_t>& outSmaskBuffer,
int compressionLevel = 6) {
auto err = ExportRgbToMemory(separated, outRgbBuffer, compressionLevel);
if (err != ErrorCode::Success) {
return err;
}
if (!separated.alphaData.empty()) {
err = ExportSmaskToMemory(separated, outSmaskBuffer, compressionLevel);
if (err != ErrorCode::Success) {
return err;
}
}
return ErrorCode::Success;
}
#if 1
ErrorCode ExportPngWithSmask(const DIBitmapConverter::ImageData& dibData,
std::vector<uint8_t>& outPng,
int compressionLevel = 6) {
if (dibData.rgbData.empty() || dibData.width == 0 || dibData.height == 0) {
return ErrorCode::InvalidParameters;
}
bool hasAlphaInRgb = dibData.rgbData.size() == dibData.width * dibData.height * 4;
bool hasNoAlphaInRgb = dibData.rgbData.size() == dibData.width * dibData.height * 3;
if (!hasAlphaInRgb && !hasNoAlphaInRgb) {
return ErrorCode::InvalidParameters;
}
if (dibData.hasAlpha && !dibData.smaskData.empty()) {
if (dibData.smaskData.size() < dibData.width * dibData.height) {
return ErrorCode::InvalidParameters;
}
}
png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
if (!png_ptr) {
return ErrorCode::CreateWriteStructFailed;
}
png_infop info_ptr = png_create_info_struct(png_ptr);
if (!info_ptr) {
png_destroy_write_struct(&png_ptr, nullptr);
return ErrorCode::CreateInfoStructFailed;
}
if (setjmp(png_jmpbuf(png_ptr))) {
png_destroy_write_struct(&png_ptr, &info_ptr);
return ErrorCode::PngProcessingError;
}
struct PngWriteBuffer {
std::vector<uint8_t>* buffer;
size_t pos;
};
PngWriteBuffer write_buffer = { &outPng, 0 };
auto write_data = [](png_structp png_ptr, png_bytep data, png_size_t length) {
PngWriteBuffer* buffer = static_cast<PngWriteBuffer*>(png_get_io_ptr(png_ptr));
buffer->buffer->resize(buffer->pos + length);
memcpy(&((*buffer->buffer)[buffer->pos]), data, length);
buffer->pos += length;
};
auto flush_data = [](png_structp png_ptr) {};
png_set_write_fn(png_ptr, &write_buffer, write_data, flush_data);
png_set_compression_level(png_ptr, compressionLevel);
png_set_IHDR(png_ptr, info_ptr, dibData.width, dibData.height,
8, PNG_COLOR_TYPE_RGBA,
PNG_INTERLACE_NONE,
PNG_COMPRESSION_TYPE_DEFAULT,
PNG_FILTER_TYPE_DEFAULT);
std::vector<png_bytep> row_pointers(dibData.height);
std::vector<uint8_t> rgbaData(dibData.width * dibData.height * 4);
if (hasAlphaInRgb) {
for (uint32_t y = 0; y < dibData.height; ++y) {
for (uint32_t x = 0; x < dibData.width; ++x) {
size_t srcOffset = (y * dibData.width + x) * 4;
size_t dstOffset = (y * dibData.width + x) * 4;
rgbaData[dstOffset] = dibData.rgbData[srcOffset];
rgbaData[dstOffset + 1] = dibData.rgbData[srcOffset + 1];
rgbaData[dstOffset + 2] = dibData.rgbData[srcOffset + 2];
if (dibData.hasAlpha && !dibData.smaskData.empty()) {
rgbaData[dstOffset + 3] = dibData.smaskData[y * dibData.width + x];
}
else {
rgbaData[dstOffset + 3] = dibData.rgbData[srcOffset + 3];
}
}
}
}
else {
for (uint32_t y = 0; y < dibData.height; ++y) {
for (uint32_t x = 0; x < dibData.width; ++x) {
size_t srcOffset = (y * dibData.width + x) * 3;
size_t dstOffset = (y * dibData.width + x) * 4;
rgbaData[dstOffset] = dibData.rgbData[srcOffset];
rgbaData[dstOffset + 1] = dibData.rgbData[srcOffset + 1];
rgbaData[dstOffset + 2] = dibData.rgbData[srcOffset + 2];
if (dibData.hasAlpha && !dibData.smaskData.empty()) {
rgbaData[dstOffset + 3] = dibData.smaskData[y * dibData.width + x];
}
else {
rgbaData[dstOffset + 3] = 0xFF;
}
}
}
}
for (uint32_t y = 0; y < dibData.height; ++y) {
row_pointers[y] = rgbaData.data() + y * dibData.width * 4;
}
png_write_info(png_ptr, info_ptr);
png_write_image(png_ptr, row_pointers.data());
png_write_end(png_ptr, nullptr);
png_destroy_write_struct(&png_ptr, &info_ptr);
return ErrorCode::Success;
}
ErrorCode ExportRgbDataToPng(const DIBitmapConverter::ImageData& dibData,
std::vector<uint8_t>& outRgbPng,
int compressionLevel = 6) {
if (dibData.rgbData.empty() || dibData.width == 0 || dibData.height == 0) {
return ErrorCode::InvalidParameters;
}
size_t requiredSize = dibData.width * dibData.height * 3;
if (dibData.rgbData.size() < requiredSize) {
return ErrorCode::InvalidParameters;
}
png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
if (!png_ptr) return ErrorCode::CreateWriteStructFailed;
png_infop info_ptr = png_create_info_struct(png_ptr);
if (!info_ptr) {
png_destroy_write_struct(&png_ptr, nullptr);
return ErrorCode::CreateInfoStructFailed;
}
if (setjmp(png_jmpbuf(png_ptr))) {
png_destroy_write_struct(&png_ptr, &info_ptr);
return ErrorCode::PngProcessingError;
}
struct WriteContext {
std::vector<uint8_t>* buffer;
size_t offset = 0;
} ctx{ &outRgbPng };
png_set_write_fn(png_ptr, &ctx, [](png_structp png, png_bytep data, png_size_t len) {
auto* c = static_cast<WriteContext*>(png_get_io_ptr(png));
c->buffer->resize(c->offset + len);
memcpy(c->buffer->data() + c->offset, data, len);
c->offset += len;
}, nullptr);
png_set_compression_level(png_ptr, compressionLevel);
png_set_IHDR(png_ptr, info_ptr, dibData.width, dibData.height,
8, PNG_COLOR_TYPE_RGB,
PNG_INTERLACE_NONE,
PNG_COMPRESSION_TYPE_DEFAULT,
PNG_FILTER_TYPE_DEFAULT);
std::vector<png_bytep> row_pointers(dibData.height);
std::vector<uint8_t> contiguous_rgb_data;
if (dibData.hasAlpha && dibData.rgbData.size() >= dibData.width * dibData.height * 4) {
contiguous_rgb_data.resize(dibData.width * dibData.height * 3);
for (uint32_t i = 0; i < dibData.width * dibData.height; ++i) {
contiguous_rgb_data[i * 3] = dibData.rgbData[i * 4];
contiguous_rgb_data[i * 3 + 1] = dibData.rgbData[i * 4 + 1];
contiguous_rgb_data[i * 3 + 2] = dibData.rgbData[i * 4 + 2];
}
}
else {
contiguous_rgb_data = dibData.rgbData;
}
for (uint32_t y = 0; y < dibData.height; ++y) {
row_pointers[y] = contiguous_rgb_data.data() + y * dibData.width * 3;
}
png_write_info(png_ptr, info_ptr);
png_write_image(png_ptr, row_pointers.data());
png_write_end(png_ptr, nullptr);
png_destroy_write_struct(&png_ptr, &info_ptr);
return ErrorCode::Success;
}
ErrorCode ExportSmaskToPng(const DIBitmapConverter::ImageData& dibData,
std::vector<uint8_t>& outSmaskPng,
int compressionLevel = 6) {
if (!dibData.hasAlpha || dibData.smaskData.empty() ||
dibData.width == 0 || dibData.height == 0) {
return ErrorCode::InvalidParameters;
}
if (dibData.smaskData.size() < dibData.width * dibData.height) {
return ErrorCode::InvalidParameters;
}
png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
if (!png_ptr) return ErrorCode::CreateWriteStructFailed;
png_infop info_ptr = png_create_info_struct(png_ptr);
if (!info_ptr) {
png_destroy_write_struct(&png_ptr, nullptr);
return ErrorCode::CreateInfoStructFailed;
}
if (setjmp(png_jmpbuf(png_ptr))) {
png_destroy_write_struct(&png_ptr, &info_ptr);
return ErrorCode::PngProcessingError;
}
struct WriteContext {
std::vector<uint8_t>* buffer;
size_t offset = 0;
} ctx{ &outSmaskPng };
png_set_write_fn(png_ptr, &ctx, [](png_structp png, png_bytep data, png_size_t len) {
auto* c = static_cast<WriteContext*>(png_get_io_ptr(png));
c->buffer->resize(c->offset + len);
memcpy(c->buffer->data() + c->offset, data, len);
c->offset += len;
}, nullptr);
png_set_compression_level(png_ptr, compressionLevel);
png_set_IHDR(png_ptr, info_ptr, dibData.width, dibData.height,
8, PNG_COLOR_TYPE_GRAY,
PNG_INTERLACE_NONE,
PNG_COMPRESSION_TYPE_DEFAULT,
PNG_FILTER_TYPE_DEFAULT);
std::vector<png_bytep> row_pointers(dibData.height);
for (uint32_t y = 0; y < dibData.height; ++y) {
row_pointers[y] = const_cast<uint8_t*>(dibData.smaskData.data()) + y * dibData.width;
}
png_write_info(png_ptr, info_ptr);
png_write_image(png_ptr, row_pointers.data());
png_write_end(png_ptr, nullptr);
png_destroy_write_struct(&png_ptr, &info_ptr);
return ErrorCode::Success;
}
ErrorCode ExportSeparatedChannels(const DIBitmapConverter::ImageData& dibData,
std::vector<uint8_t>& outRgbPng,
std::vector<uint8_t>& outSmaskPng,
int compressionLevel = 6) {
auto err = ExportRgbDataToPng(dibData, outRgbPng, compressionLevel);
if (err != ErrorCode::Success) return err;
if (dibData.hasAlpha && !dibData.smaskData.empty()) {
err = ExportSmaskToPng(dibData, outSmaskPng, compressionLevel);
}
return err;
}
void ExtractImageDataFromDIBitmap(
CFX_DIBitmap* sourceBitmap,
PngManager::ImageData& imageData)
{
DIBitmapConverter::ImageData convertedData =
DIBitmapConverter::GetImageData(sourceBitmap, true);
imageData.width = convertedData.width;
imageData.height = convertedData.height;
imageData.bitDepth = 8;
imageData.format = PngManager::PixelFormat::RGBA;
const bool hasAlpha = !convertedData.smaskData.empty() &&
convertedData.smaskData.size() >= convertedData.width * convertedData.height;
const size_t pixelCount = convertedData.width * convertedData.height;
imageData.pixels.resize(pixelCount * 4);
for (size_t i = 0; i < pixelCount; i++) {
const size_t rgbOffset = hasAlpha ? i * 4 : i * 3;
const size_t rgbaOffset = i * 4;
if (convertedData.rgbData.size() > rgbOffset + 2) {
imageData.pixels[rgbaOffset] = convertedData.rgbData[rgbOffset];
imageData.pixels[rgbaOffset + 1] = convertedData.rgbData[rgbOffset + 1];
imageData.pixels[rgbaOffset + 2] = convertedData.rgbData[rgbOffset + 2];
}
else {
imageData.pixels[rgbaOffset] = 0;
imageData.pixels[rgbaOffset + 1] = 0;
imageData.pixels[rgbaOffset + 2] = 0;
}
if (hasAlpha && convertedData.smaskData.size() > i) {
imageData.pixels[rgbaOffset + 3] = convertedData.smaskData[i];
}
else {
imageData.pixels[rgbaOffset + 3] = 0xFF;
}
}
}
void ExtractRGBDataFromDIBitmap(
CFX_DIBitmap* sourceBitmap,
PngManager::ImageData& imageData)
{
DIBitmapConverter::ImageData convertedData =
DIBitmapConverter::GetImageData(sourceBitmap, false);
imageData.width = convertedData.width;
imageData.height = convertedData.height;
imageData.bitDepth = 8;
imageData.format = PngManager::PixelFormat::RGB;
const size_t pixelCount = convertedData.width * convertedData.height;
imageData.pixels.resize(pixelCount * 3);
for (size_t i = 0; i < pixelCount; i++) {
const size_t srcOffset = i * 3;
const size_t destOffset = i * 3;
if (convertedData.rgbData.size() > srcOffset + 2) {
imageData.pixels[destOffset] = convertedData.rgbData[srcOffset];
imageData.pixels[destOffset + 1] = convertedData.rgbData[srcOffset + 1];
imageData.pixels[destOffset + 2] = convertedData.rgbData[srcOffset + 2];
}
else {
imageData.pixels[destOffset] = 0;
imageData.pixels[destOffset + 1] = 0;
imageData.pixels[destOffset + 2] = 0;
}
}
}
#endif
private:
PngManager(const PngManager&) = delete;
PngManager& operator=(const PngManager&) = delete;
};
#include <vector>
#include <cmath>
#include <algorithm>
#include <stdexcept>
class ExtendedPngManager : public PngManager {
public:
PngManager::ErrorCode WriteIndexedToMemory(
uint32_t width, uint32_t height,
const std::vector<uint8_t>& indexes,
const std::vector<png_color>& palette,
const std::vector<uint8_t>& trans,
std::vector<uint8_t>& outBuffer,
int compressionLevel = 6)
{
if (indexes.size() != width * height) {
return PngManager::ErrorCode::InvalidParameters;
}
if (palette.empty() || palette.size() > 256) {
return PngManager::ErrorCode::InvalidParameters;
}
png_structp pngPtr = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
if (!pngPtr) {
return PngManager::ErrorCode::CreateWriteStructFailed;
}
png_infop infoPtr = png_create_info_struct(pngPtr);
if (!infoPtr) {
png_destroy_write_struct(&pngPtr, nullptr);
return PngManager::ErrorCode::CreateInfoStructFailed;
}
if (setjmp(png_jmpbuf(pngPtr))) {
png_destroy_write_struct(&pngPtr, &infoPtr);
return PngManager::ErrorCode::PngProcessingError;
}
struct WriteContext {
std::vector<uint8_t>* buffer;
} ctx{ &outBuffer };
png_set_write_fn(pngPtr, &ctx, [](png_structp pngPtr, png_bytep data, png_size_t length) {
auto* ctx = static_cast<WriteContext*>(png_get_io_ptr(pngPtr));
size_t oldSize = ctx->buffer->size();
ctx->buffer->resize(oldSize + length);
memcpy(ctx->buffer->data() + oldSize, data, length);
}, nullptr);
png_set_compression_level(pngPtr, compressionLevel);
png_set_IHDR(pngPtr, infoPtr, width, height, 8,
PNG_COLOR_TYPE_PALETTE,
PNG_INTERLACE_NONE,
PNG_COMPRESSION_TYPE_DEFAULT,
PNG_FILTER_TYPE_DEFAULT);
png_color paletteArray[256];
int paletteSize = static_cast<int>(palette.size());
if (paletteSize > 256) paletteSize = 256;
for (int i = 0; i < paletteSize; i++) {
paletteArray[i] = palette[i];
}
png_set_PLTE(pngPtr, infoPtr, paletteArray, paletteSize);
if (!trans.empty()) {
int transSize = static_cast<int>(trans.size()) < paletteSize ? static_cast<int>(trans.size()) : paletteSize;
png_byte transArray[256];
for (int i = 0; i < transSize; i++) {
transArray[i] = trans[i];
}
png_set_tRNS(pngPtr, infoPtr, transArray, transSize, nullptr);
}
png_write_info(pngPtr, infoPtr);
std::vector<png_bytep> rowPointers(height);
for (uint32_t y = 0; y < height; ++y) {
rowPointers[y] = const_cast<png_bytep>(&indexes[y * width]);
}
png_write_image(pngPtr, rowPointers.data());
png_write_end(pngPtr, nullptr);
png_destroy_write_struct(&pngPtr, &infoPtr);
return PngManager::ErrorCode::Success;
}
PngManager::ErrorCode WriteIndexedToFile(
uint32_t width, uint32_t height,
const std::vector<uint8_t>& indexes,
const std::vector<png_color>& palette,
const std::vector<uint8_t>& trans,
std::string path,
int compressionLevel = 6)
{
std::vector<uint8_t> outBuffer;
ErrorCode err = WriteIndexedToMemory(width, height, indexes, palette, trans, outBuffer, compressionLevel);
if (err != ErrorCode::Success) {
return err;
}
std::ofstream file(path, std::ios::binary);
if (!file) {
return ErrorCode::PngProcessingError;
}
file.write(reinterpret_cast<const char*>(outBuffer.data()), outBuffer.size());
return ErrorCode::Success;
}
};
#if defined(_WIN32) || defined(_WIN64)
#pragma comment(lib, "ws2_32.lib")
#pragma comment(lib, "advapi32.lib")
#pragma comment(lib, "userenv.lib")
#pragma comment(lib, "ntdll.lib")
#endif
#if !defined(__ANDROID__) && !(defined(__APPLE__) && TARGET_OS_IPHONE)
class PNGQuantizer {
public:
struct CompressionParams {
int max_colors = 256;
float dither_level = 1.0f;
int speed = 5;
int compression_level = 9;
int quality_min = 0;
int quality_max = 100;
int posterization = 0;
int min_opacity = 0;
};
bool compress(const std::vector<uint8_t>& input_png_data,
std::vector<uint8_t>& output_png_data,
CompressionParams params) {
ExtendedPngManager pngManager;
PngManager::ImageData imageData;
auto error = pngManager.ReadFromMemory(input_png_data.data(),
input_png_data.size(),
imageData);
if (error != PngManager::ErrorCode::Success) {
return false;
}
const int width = static_cast<int>(imageData.width);
const int height = static_cast<int>(imageData.height);
const bool has_alpha = (imageData.format == PngManager::PixelFormat::RGBA);
UniqueLiqAttr attr(liq_attr_create());
liq_set_max_colors(attr.get(), params.max_colors);
liq_set_speed(attr.get(), params.speed);
liq_set_max_colors(attr.get(), params.max_colors);
liq_set_speed(attr.get(), params.speed);
liq_set_quality(attr.get(), params.quality_min, params.quality_max);
liq_set_min_posterization(attr.get(), params.posterization);
if (has_alpha) {
liq_set_min_opacity(attr.get(), params.min_opacity);
}
UniqueLiqImage image(liq_image_create_rgba(attr.get(),
imageData.pixels.data(),
width, height, 0));
UniqueLiqResult result(liq_quantize_image(attr.get(), image.get()));
liq_set_dithering_level(result.get(), params.dither_level);
std::vector<uint8_t> indexed_data(width * height);
liq_write_remapped_image(result.get(),
image.get(),
indexed_data.data(),
indexed_data.size());
const liq_palette* palette = liq_get_palette(result.get());
std::vector<png_color> pngPalette;
std::vector<uint8_t> alpha_channel;
for (int i = 0; i < palette->count; i++) {
pngPalette.push_back({
palette->entries[i].r,
palette->entries[i].g,
palette->entries[i].b
});
if (has_alpha) {
alpha_channel.push_back(palette->entries[i].a);
}
}
error = pngManager.WriteIndexedToMemory(
width, height,
indexed_data,
pngPalette,
alpha_channel,
output_png_data,
params.compression_level
);
return error == PngManager::ErrorCode::Success;
}
CompressionParams GetMaxCompressionParams() {
PNGQuantizer::CompressionParams params;
params.max_colors = 32;
params.posterization = 2;
params.quality_min = 0;
params.quality_max = 20;
params.speed = 10;
params.dither_level = 0.8f;
params.min_opacity = 30;
params.compression_level = 9;
return params;
}
private:
struct LiqAttrDeleter {
void operator()(liq_attr* attr) const {
if (attr) liq_attr_destroy(attr);
}
};
struct LiqImageDeleter {
void operator()(liq_image* img) const {
if (img) liq_image_destroy(img);
}
};
struct LiqResultDeleter {
void operator()(liq_result* res) const {
if (res) liq_result_destroy(res);
}
};
using UniqueLiqAttr = std::unique_ptr<liq_attr, LiqAttrDeleter>;
using UniqueLiqImage = std::unique_ptr<liq_image, LiqImageDeleter>;
using UniqueLiqResult = std::unique_ptr<liq_result, LiqResultDeleter>;
};
#endif
void PDFCorePngManager::ExtractPngDataFromDIBitmap(CFX_DIBitmap* sourceBitmap, std::vector<uint8_t>& png_data, int compressionLevel)
{
auto imageData = DIBitmapConverter::GetImageData(sourceBitmap, true);
PngManager png_manager;
png_manager.ExportPngWithSmask(imageData, png_data, compressionLevel);
}
void PDFCorePngManager::ExtractSmaskDataFromDIBitmap(CFX_DIBitmap* sourceBitmap, std::vector<uint8_t>& smask_image_data)
{
auto imageData = DIBitmapConverter::GetImageData(sourceBitmap, true);
PngManager png_manager;
png_manager.ExportSmaskToPng(imageData, smask_image_data);
}
void PDFCorePngManager::ExtractRgbDataFromDIBitmap(CFX_DIBitmap* sourceBitmap, std::vector<uint8_t>& rgb_image_data)
{
auto imageData = DIBitmapConverter::GetImageData(sourceBitmap, true);
PngManager png_manager;
png_manager.ExportRgbDataToPng(imageData, rgb_image_data);
}
void PDFCorePngManager::ExtractRgbSmaskDataFromDIBitmap(CFX_DIBitmap* sourceBitmap, std::vector<uint8_t>& rgb_image_data, std::vector<uint8_t>& smask_image_data)
{
auto imageData = DIBitmapConverter::GetImageData(sourceBitmap, true);
PngManager png_manager;
png_manager.ExportSeparatedChannels(imageData, rgb_image_data, smask_image_data);
}
#if !defined(__ANDROID__) && !(defined(__APPLE__) && TARGET_OS_IPHONE)
bool PDFCorePngManager::PNGQuantizerCompress(std::vector<uint8_t>& input_data, std::vector<uint8_t>& output_data)
{
PNGQuantizer quantizer;
PNGQuantizer::CompressionParams compress_param = quantizer.GetMaxCompressionParams();
return quantizer.compress(input_data, output_data, compress_param);
}
#endif
bool PDFCorePngManager::ExtractImageDataFromDIBitmap(CFX_DIBitmap* sourceBitmap, std::vector<uint8_t>& image_pixel_data, uint32_t& width, uint32_t& height, uint8_t& bitDepth)
{
PngManager png_manager;
PngManager::ImageData sourceImage;
png_manager.ExtractImageDataFromDIBitmap(sourceBitmap, sourceImage);
width = sourceImage.width;
height = sourceImage.height;
bitDepth = sourceImage.bitDepth;
image_pixel_data = std::move(sourceImage.pixels);
return !image_pixel_data.empty();
}
bool PDFCorePngManager::ExtractRGBDataFromDIBitmap(CFX_DIBitmap* sourceBitmap, std::vector<uint8_t>& image_pixel_data, uint32_t &width, uint32_t &height, uint8_t &bitDepth)
{
PngManager png_manager;
PngManager::ImageData sourceImage;
png_manager.ExtractRGBDataFromDIBitmap(sourceBitmap, sourceImage);
width = sourceImage.width;
height = sourceImage.height;
bitDepth = sourceImage.bitDepth;
image_pixel_data = std::move(sourceImage.pixels);
return !image_pixel_data.empty();
}
bool PDFCorePngManager::WriteVectorToFile(const std::vector<uint8_t>& data, const std::string& filename) {
std::ofstream outfile(filename, std::ios::binary | std::ios::trunc);
if (!outfile.is_open()) {
return false;
}
outfile.write(reinterpret_cast<const char*>(data.data()), data.size());
if (!outfile.good()) {
outfile.close();
return false;
}
outfile.close();
return true;
}
bool static ReadVectorFromFile(std::vector<uint8_t>& data, const std::string& filename) {
data.clear();
std::ifstream infile(filename, std::ios::binary);
if (!infile.is_open()) {
return false;
}
infile.seekg(0, std::ios::end);
std::streamsize size = infile.tellg();
infile.seekg(0, std::ios::beg);
if (size == -1) {
infile.close();
return false;
}
data.reserve(static_cast<size_t>(size));
data.assign(std::istreambuf_iterator<char>(infile), std::istreambuf_iterator<char>());
if (!infile.good()) {
infile.close();
return false;
}
infile.close();
return true;
}
ImageInterpolator::Method static MapInterpolationMethod(
PDFCorePngManager::PDFCoreInterpolationMethod foxitMethod)
{
switch (foxitMethod) {
case PDFCorePngManager::PDFCoreInterpolationMethod::BICUBIC:
return ImageInterpolator::Method::BICUBIC;
case PDFCorePngManager::PDFCoreInterpolationMethod::BILINEAR:
return ImageInterpolator::Method::BILINEAR;
case PDFCorePngManager::PDFCoreInterpolationMethod::NEAREST_NEIGHBOR:
default:
return ImageInterpolator::Method::NEAREST_NEIGHBOR;
}
}
bool static ResizeImageData(
const PngManager::ImageData& source,
PngManager::ImageData& target,
ImageInterpolator::Method method,
bool use_multithread,
uint8_t channels = 4)
{
if (source.pixels.empty() || source.width == 0 || source.height == 0) {
return false;
}
if (target.width == 0 || target.height == 0) {
return false;
}
if (channels != 3 && channels != 4) {
return false;
}
if (source.pixels.size() != source.width * source.height * channels) {
return false;
}
ImageInterpolator::ImageData input;
input.width = source.width;
input.height = source.height;
input.bitDepth = source.bitDepth;
input.channels = channels;
input.pixels = source.pixels;
ImageInterpolator::ImageData output =
ImageInterpolator::resizeCopy(input, target.width, target.height, method);
if (output.pixels.empty() ||
output.width != target.width ||
output.height != target.height ||
output.pixels.size() != target.width * target.height * channels) {
return false;
}
target.width = output.width;
target.height = output.height;
target.bitDepth = output.bitDepth;
target.pixels = std::move(output.pixels);
return true;
}
std::vector<uint8_t> PDFCorePngManager::ResizePngImage(
CFX_DIBitmap* sourceBitmap,
PDFCoreInterpolationMethod pdfore_interpolation_method,
int WidthScaleFactor)
{
if (!sourceBitmap || WidthScaleFactor <= 0) {
return {};
}
PngManager::ImageData sourceImage;
PngManager png_manager;
png_manager.ExtractImageDataFromDIBitmap(sourceBitmap, sourceImage);
if (sourceImage.pixels.empty() || sourceImage.width == 0 || sourceImage.height == 0) {
return {};
}
uint32_t targetWidth = (std::max)(1, static_cast<int>(sourceImage.width) / WidthScaleFactor);
uint32_t targetHeight = (std::max)(1, static_cast<int>(sourceImage.height) / WidthScaleFactor);
ImageInterpolator::Method interpolationMethod =
MapInterpolationMethod(pdfore_interpolation_method);
PngManager::ImageData targetImage;
targetImage.width = targetWidth;
targetImage.height = targetHeight;
targetImage.bitDepth = sourceImage.bitDepth;
targetImage.format = sourceImage.format;
if (!ResizeImageData(sourceImage, targetImage, interpolationMethod, false , 4)) {
return {};
}
std::vector<uint8_t> outputPng;
PngManager pngManager;
if (pngManager.WriteToMemory(targetImage, outputPng) != PngManager::ErrorCode::Success) {
return {};
}
return outputPng;
}
bool PDFCorePngManager::ResizeImageToPixel(
CFX_DIBitmap* sourceBitmap,
PDFCoreInterpolationMethod pdfore_interpolation_method,
int WidthScaleFactor,
uint32_t& outWidth,
uint32_t& outHeight,
uint8_t& outBitDepth,
int& outFormat,
std::vector<uint8_t>& outPixels,
bool isJpegFormat,
bool use_multithread)
{
if (!sourceBitmap || WidthScaleFactor <= 0) {
return false;
}
PngManager::PixelFormat pix_format;
PngManager::ImageData sourceImage;
PngManager png_manager;
if (isJpegFormat) {
png_manager.ExtractRGBDataFromDIBitmap(sourceBitmap, sourceImage);
}
else {
png_manager.ExtractImageDataFromDIBitmap(sourceBitmap, sourceImage);
}
if (sourceImage.pixels.empty() || sourceImage.width == 0 || sourceImage.height == 0) {
return false;
}
outWidth = (std::max)(1, static_cast<int>(sourceImage.width) / WidthScaleFactor);
outHeight = (std::max)(1, static_cast<int>(sourceImage.height) / WidthScaleFactor);
outBitDepth = sourceImage.bitDepth;
pix_format = sourceImage.format;
PngManager::ImageData targetImage;
targetImage.width = outWidth;
targetImage.height = outHeight;
targetImage.bitDepth = outBitDepth;
targetImage.format = pix_format;
int channels = isJpegFormat ? 3 : (pix_format == PngManager::PixelFormat::RGBA ? 4 : 3);
targetImage.pixels.reserve(outWidth * outHeight * channels);
if (use_multithread)
{
if (!ResizeImageData(sourceImage, targetImage,
MapInterpolationMethod(pdfore_interpolation_method), true,channels)) {
return false;
}
}
else {
if (!ResizeImageData(sourceImage, targetImage,
MapInterpolationMethod(pdfore_interpolation_method), false,channels)) {
return false;
}
}
outPixels = std::move(targetImage.pixels);
outFormat = static_cast<int>(pix_format);
return true;
}