Skip to content

Commit

Permalink
Merge pull request #255 from Deledrius/image_functions
Browse files Browse the repository at this point in the history
Add missing functions to read PNGs and JPGs.
  • Loading branch information
zrax authored Mar 7, 2024
2 parents 82a6b6c + b735289 commit e043a0e
Show file tree
Hide file tree
Showing 6 changed files with 268 additions and 20 deletions.
2 changes: 1 addition & 1 deletion core/PRP/Surface/plMipmap.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -632,7 +632,7 @@ size_t plMipmap::GetUncompressedSize(size_t level) const
return lvl.fHeight * lvl.fWidth * (fPixelSize / 8);
}

void plMipmap::DecompressImage(size_t level, void* dest, size_t size)
void plMipmap::DecompressImage(size_t level, void* dest, size_t size) const
{
const LevelData& lvl = fLevelData[level];

Expand Down
2 changes: 1 addition & 1 deletion core/PRP/Surface/plMipmap.h
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ class HSPLASMA_EXPORT plMipmap : public plBitmap
bool isAlphaJPEG() const { return !fJAlphaCache.empty(); }

size_t GetUncompressedSize(size_t level) const;
void DecompressImage(size_t level, void* dest, size_t size);
void DecompressImage(size_t level, void* dest, size_t size) const;
void CompressImage(size_t level, void* src, size_t size, BlockQuality quality = kBlockQualityNormal);
};

Expand Down
165 changes: 150 additions & 15 deletions core/Util/plJPEG.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,18 @@

#include "plJPEG.h"
#include "Debug/plDebug.h"
#include "PRP/Surface/plMipmap.h"

#include <cstring>
#include <memory>

extern "C" {
#include <jerror.h>
}


// Input Stream interface

#define INPUT_BUF_SIZE 4096

/* hsStream JPEG source -- modelled after IJG's stdio src */
Expand Down Expand Up @@ -107,6 +113,73 @@ GLOBAL(void) jpeg_hsStream_src(j_decompress_ptr dinfo, hsStream* S)
src->pub.next_input_byte = nullptr;
}


// Output Stream interface

#define OUTPUT_BUF_SIZE 4096

/* hsStream JPEG destination -- modelled after IJG's stdio dest */
struct jpeg_hsStream_destination
{
struct jpeg_destination_mgr pub;
hsStream* stream;
JOCTET* buffer;
boolean start_of_stream;
};

METHODDEF(void) init_hsStream_destination(j_compress_ptr cinfo)
{
jpeg_hsStream_destination* dest = (jpeg_hsStream_destination*)cinfo->dest;
dest->start_of_stream = TRUE;
}

METHODDEF(boolean) hsStream_empty_output_buffer(j_compress_ptr cinfo)
{
jpeg_hsStream_destination* dest = (jpeg_hsStream_destination*)cinfo->dest;

dest->stream->write(OUTPUT_BUF_SIZE, dest->buffer);

memset(dest->buffer, 0, OUTPUT_BUF_SIZE);

dest->pub.next_output_byte = dest->buffer;
dest->pub.free_in_buffer = OUTPUT_BUF_SIZE;
dest->start_of_stream = FALSE;

return TRUE;
}

METHODDEF(void) hsStream_term_destination(j_compress_ptr cinfo)
{
jpeg_hsStream_destination* dest = (jpeg_hsStream_destination*)cinfo->dest;
size_t datacount = OUTPUT_BUF_SIZE - dest->pub.free_in_buffer;
if (datacount > 0)
dest->stream->write(datacount, dest->buffer);
dest->stream->flush();
}

GLOBAL(void) jpeg_hsStream_dest(j_compress_ptr cinfo, hsStream* S)
{
jpeg_hsStream_destination* dest;

if (cinfo->dest == nullptr) {
cinfo->dest = (struct jpeg_destination_mgr*)
cinfo->mem->alloc_small((j_common_ptr)cinfo, JPOOL_PERMANENT,
sizeof(jpeg_hsStream_destination));
dest = (jpeg_hsStream_destination*)cinfo->dest;
dest->buffer = (JOCTET*)
cinfo->mem->alloc_small((j_common_ptr)cinfo, JPOOL_PERMANENT,
OUTPUT_BUF_SIZE * sizeof(JOCTET));
}

dest = (jpeg_hsStream_destination*)cinfo->dest;
dest->pub.init_destination = init_hsStream_destination;
dest->pub.empty_output_buffer = hsStream_empty_output_buffer;
dest->pub.term_destination = hsStream_term_destination;
dest->stream = S;
dest->pub.free_in_buffer = OUTPUT_BUF_SIZE;
dest->pub.next_output_byte = dest->buffer;
}

// JPEG error handler for libPlasma
static char jpeg_error_buf[JMSG_LENGTH_MAX] = { 0 };

Expand Down Expand Up @@ -172,42 +245,104 @@ void plJPEG::DecompressJPEG(hsStream* S, void* buf, size_t size)

jpeg_hsStream_src(&ji.dinfo, S);
jpeg_read_header(&ji.dinfo, TRUE);
ji.dinfo.out_color_space = JCS_EXT_BGRA; // Data stored as RGB on disk but Plasma uses BGR
jpeg_start_decompress(&ji.dinfo);

int row_stride = ji.dinfo.output_width * ji.dinfo.output_components;
int out_stride = ji.dinfo.output_width * 4; // Always decompress to RGBA
int row_stride = ji.dinfo.output_width * ji.dinfo.out_color_components;
int out_stride = row_stride;
RAII_JSAMPROW<1> jbuffer(row_stride);

size_t offs = 0;
while (ji.dinfo.output_scanline < ji.dinfo.output_height) {
if (offs + out_stride > size)
throw hsJPEGException(__FILE__, __LINE__, "buffer overflow");
jpeg_read_scanlines(&ji.dinfo, jbuffer.data, 1);
memset(((unsigned char*)buf) + offs, 0, out_stride);
for (size_t x = 0; x<ji.dinfo.output_width; x++) {
memcpy(((unsigned char*)buf) + offs + (x * 4),
jbuffer.data[0] + (x * ji.dinfo.output_components),
jbuffer.data[0] + (x * ji.dinfo.out_color_components),
ji.dinfo.out_color_components);
}
offs += out_stride;
}

// Data stored as RGB on disk but Plasma uses BGR
if (reinterpret_cast<uintptr_t>(buf) % alignof(uint32_t) != 0)
throw hsBadParamException(__FILE__, __LINE__, "buf should be aligned on a 32-bit boundary");
jpeg_finish_decompress(&ji.dinfo);
}


plMipmap* plJPEG::DecompressJPEG(hsStream* S)
{
plJPEG& ji = Instance();

uint32_t* dp = reinterpret_cast<uint32_t*>(buf);
for (size_t i=0; i<size; i += 4) {
*dp = (*dp & 0xFF00FF00)
| (*dp & 0x00FF0000) >> 16
| (*dp & 0x000000FF) << 16;
dp++;
jpeg_hsStream_src(&ji.dinfo, S);
jpeg_read_header(&ji.dinfo, TRUE);
ji.dinfo.out_color_space = JCS_EXT_BGRA; // Data stored as RGB on disk but Plasma uses BGR
jpeg_start_decompress(&ji.dinfo);

int row_stride = ji.dinfo.output_width * ji.dinfo.out_color_components;
int out_stride = row_stride;
RAII_JSAMPROW<1> jbuffer(row_stride);

// Start with a reasonable size for the buffer
auto buffer_size = ji.dinfo.output_width * ji.dinfo.output_height * ji.dinfo.out_color_components;
auto buffer = std::make_unique<unsigned char[]>(buffer_size);

size_t offs = 0;
while (ji.dinfo.output_scanline < ji.dinfo.output_height) {
jpeg_read_scanlines(&ji.dinfo, jbuffer.data, 1);
for (size_t x = 0; x < ji.dinfo.output_width; x++) {
memcpy(buffer.get() + offs + (x * 4),
jbuffer.data[0] + (x * ji.dinfo.out_color_components),
ji.dinfo.out_color_components);
}
offs += out_stride;
}

jpeg_finish_decompress(&ji.dinfo);

plMipmap* newMipmap = new plMipmap(ji.dinfo.output_width, ji.dinfo.output_height, 1, plMipmap::kUncompressed, plMipmap::kRGB8888);
newMipmap->setImageData(buffer.get(), buffer_size);

return newMipmap;
}

void plJPEG::CompressJPEG(hsStream* S, void* buf, size_t size)
void plJPEG::CompressJPEG(hsStream* S, void* buf, size_t size, uint32_t width, uint32_t height, uint32_t bpp)
{
throw hsNotImplementedException(__FILE__, __LINE__);
plJPEG& ji = Instance();

JSAMPLE* image_buffer = reinterpret_cast<JSAMPLE*>(buf);

jpeg_create_compress(&ji.cinfo);
jpeg_hsStream_dest(&ji.cinfo, S);

ji.cinfo.image_width = width;
ji.cinfo.image_height = height;
ji.cinfo.input_components = bpp / 8;
if (ji.cinfo.input_components == 4)
ji.cinfo.in_color_space = JCS_EXT_RGBX;
else
ji.cinfo.in_color_space = JCS_RGB;

jpeg_set_defaults(&ji.cinfo);
jpeg_set_quality(&ji.cinfo, 100, TRUE);
jpeg_start_compress(&ji.cinfo, TRUE);

uint32_t row_stride = ji.cinfo.image_width * ji.cinfo.input_components;
RAII_JSAMPROW<1> jbuffer(row_stride);

size_t offs = 0;
while (ji.cinfo.next_scanline < ji.cinfo.image_height) {
if (offs + row_stride > size)
throw hsJPEGException(__FILE__, __LINE__, "buffer overread");

for (size_t x = 0; x < ji.cinfo.image_width; x++) {
memcpy(jbuffer.data[0] + (x * ji.cinfo.input_components),
((unsigned char*)image_buffer) + offs + (x * ji.cinfo.input_components),
ji.cinfo.input_components);
}
(void)jpeg_write_scanlines(&ji.cinfo, jbuffer.data, 1);
offs += row_stride;
}

jpeg_finish_compress(&ji.cinfo);
jpeg_destroy_compress(&ji.cinfo);
}
15 changes: 13 additions & 2 deletions core/Util/plJPEG.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@
#ifndef _PLJPEG_H
#define _PLJPEG_H

#include "PRP/Surface/plMipmap.h"
#include "PlasmaDefs.h"
#include "Debug/hsExceptions.hpp"

extern "C" {
#include <jpeglib.h>
Expand All @@ -36,6 +37,9 @@ class hsJPEGException : public hsException
};


class hsStream;
class plMipmap;

class HSPLASMA_EXPORT plJPEG
{
private:
Expand All @@ -44,8 +48,15 @@ class HSPLASMA_EXPORT plJPEG
jpeg_error_mgr jerr;

public:
/* Read JPEG file from stream into buffer as bitmap data. */
static void DecompressJPEG(hsStream* S, void* buf, size_t size);
static void CompressJPEG(hsStream* S, void* buf, size_t size);

/* Read JPEG file from stream directly into a plMipmap. */
static plMipmap* DecompressJPEG(hsStream* S);

/* Write JPEG file to stream from bitmap data buffer. */
static void CompressJPEG(hsStream* S, void* buf, size_t size,
uint32_t width, uint32_t height, uint32_t bpp);

private:
plJPEG();
Expand Down
92 changes: 92 additions & 0 deletions core/Util/plPNG.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

#include "plPNG.h"
#include "Debug/plDebug.h"
#include "PRP/Surface/plMipmap.h"

#include <memory>

Expand Down Expand Up @@ -120,6 +121,97 @@ void plPNG::DecompressPNG(hsStream* S, void* buf, size_t size)
png_destroy_read_struct(&pngReader, &pngInfo, &endInfo);
}

plMipmap* plPNG::DecompressPNG(hsStream* S)
{
plMipmap* newMipmap = nullptr;

png_structp pngReader;
png_infop pngInfo;
png_infop endInfo;

png_byte sig[PNG_SIG_LENGTH];
S->read(sizeof(sig), sig);
if (!png_check_sig(sig, PNG_SIG_LENGTH))
throw hsPNGException(__FILE__, __LINE__, "Invalid PNG header");

pngReader = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
if (!pngReader)
throw hsPNGException(__FILE__, __LINE__, "Error initializing PNG reader");

pngInfo = png_create_info_struct(pngReader);
if (!pngInfo) {
png_destroy_read_struct(&pngReader, nullptr, nullptr);
throw hsPNGException(__FILE__, __LINE__, "Error initializing PNG info structure");
}

endInfo = png_create_info_struct(pngReader);
if (!endInfo) {
png_destroy_read_struct(&pngReader, &pngInfo, nullptr);
throw hsPNGException(__FILE__, __LINE__, "Error initializing PNG info structure");
}

png_set_read_fn(pngReader, (png_voidp)S, &pl_png_read);
png_set_sig_bytes(pngReader, PNG_SIG_LENGTH);

png_read_info(pngReader, pngInfo);
png_uint_32 pngWidth = png_get_image_width(pngReader, pngInfo);
png_uint_32 pngHeight = png_get_image_height(pngReader, pngInfo);
png_uint_32 depth = png_get_bit_depth(pngReader, pngInfo);
png_uint_32 channels = png_get_channels(pngReader, pngInfo);
png_uint_32 colorType = png_get_color_type(pngReader, pngInfo);

// Convert input to RGB
switch (colorType) {
case PNG_COLOR_TYPE_PALETTE:
png_set_palette_to_rgb(pngReader);
channels = 3;
break;

case PNG_COLOR_TYPE_GRAY:
if (depth < 8)
png_set_expand_gray_1_2_4_to_8(pngReader);
depth = 8;
break;

default:
/* Already RGB - nothing to do */
break;
}

if (png_get_valid(pngReader, pngInfo, PNG_INFO_tRNS)) {
// Convert 1-bit alpha to 8-bit alpha if necessary
png_set_tRNS_to_alpha(pngReader);
channels += 1;
} else if (channels == 3) {
// Add opaque alpha channel
png_set_filler(pngReader, 0xFF, PNG_FILLER_AFTER);
channels += 1;
}

// Plasma uses BGR for DirectX
png_set_bgr(pngReader);

/// Construct a new mipmap to hold everything
newMipmap = new plMipmap(pngWidth, pngHeight, 1, plMipmap::kUncompressed, plMipmap::kRGB8888);

char* destp = static_cast<char *>(newMipmap->getImageData());
auto row_ptrs = std::make_unique<png_bytep[]>(pngHeight);
const unsigned int stride = pngWidth * depth * channels / 8;

// Assign row pointers to the appropriate locations in the newly-created Mipmap
for (size_t i = 0; i < pngHeight; i++) {
row_ptrs[i] = (png_bytep)destp + (i * stride);
}

png_read_image(pngReader, row_ptrs.get());
png_read_end(pngReader, endInfo);

// Clean up allocated structs
png_destroy_read_struct(&pngReader, &pngInfo, &endInfo);

return newMipmap;
}

void plPNG::CompressPNG(hsStream* S, const void* buf, size_t size,
uint32_t width, uint32_t height, int pixelSize)
{
Expand Down
Loading

0 comments on commit e043a0e

Please sign in to comment.