vmware-svga/lib/util/png.c
2009-04-13 07:05:42 +00:00

338 lines
8.9 KiB
C

/**********************************************************
* Copyright 2008-2009 VMware, Inc. All rights reserved.
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use, copy,
* modify, merge, publish, distribute, sublicense, and/or sell copies
* of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
**********************************************************/
/*
* png.c --
*
* An extremely simple library for reading in-memory PNG images.
*/
#include "png.h"
#include "puff.h"
/*
*----------------------------------------------------------------------
*
* PNG_Header --
*
* Get a pointer to the header chunk of an in-memory PNG file.
*
* Results:
* Always returns a PNGChunkIHDR.
*
* Side effects:
* None.
*
*----------------------------------------------------------------------
*/
PNGChunkIHDR *
PNG_Header(void *pngData) // IN
{
return (PNGChunkIHDR*) ((uint8*)pngData + 8);
}
/*
*----------------------------------------------------------------------
*
* PNG_NextChunk --
*
* Given a pointer to one PNG chunk, return the next one.
*
* Results:
* Always returns a PNGChunk. Does not check for end-of-file.
*
* Side effects:
* None.
*
*----------------------------------------------------------------------
*/
PNGChunk *
PNG_NextChunk(PNGChunk *lastChunk) // IN
{
return (PNGChunk*) (lastChunk->data + bswap32(lastChunk->length) + 4);
}
/*
*----------------------------------------------------------------------
*
* PNGJoinIDAT --
*
* Join all of the IDAT chunks together into a contiguous block
* of compressed data.
*
* Results:
* Returns a PNGChunk header which contains the full compressed
* data content of the PNG file.
*
* Side effects:
* Modifies the PNG file, by joining all IDAT chunks.
*
*----------------------------------------------------------------------
*/
PNGChunk *
PNGJoinIDAT(PNGChunk *firstChunk) // IN
{
PNGChunk *firstIDAT = NULL;
PNGChunk *current = firstChunk;
uint8 *tail = NULL;
while (current->type != PNG_IEND) {
PNGChunk *nextChunk = PNG_NextChunk(current);
uint32 len = bswap32(current->length);
if (current->type == PNG_IDAT) {
if (firstIDAT == NULL) {
/*
* First IDAT chunk.
*/
firstIDAT = current;
tail = firstIDAT->data + len;
} else {
/*
* Additional IDAT chunk. Append the data.
*/
memcpy(tail, current->data, len);
tail += len;
}
}
current = nextChunk;
}
/*
* Fix up the IDAT header.
*/
firstIDAT->length = bswap32(tail - firstIDAT->data);
/*
* Write an IEND afterwards, so this process is repeatable.
*/
current = PNG_NextChunk(firstIDAT);
current->type = PNG_IEND;
current->length = 0;
return firstIDAT;
}
/*
*----------------------------------------------------------------------
*
* PaethPredictor --
*
* This is a simple image predictor which works based on the values
* of the pixels above, left, and to the top-left of the pixel we're
* predicting. It is defined in the PNG spec:
*
* http://www.libpng.org/pub/png/spec/1.2/PNG-Filters.html
*
* Results:
* Returns the predicted byte.
*
* Side effects:
* None.
*
*----------------------------------------------------------------------
*/
static uint8
PaethPredictor(uint8 a, // IN
uint8 b, // IN
uint8 c) // IN
{
int p = a + b - c;
int pa = p > a ? p - a : a - p;
int pb = p > b ? p - b : b - p;
int pc = p > c ? p - c : c - p;
if (pa <= pb && pa <= pc) {
return a;
} else if (pb <= pc) {
return b;
}
return c;
}
/*
*----------------------------------------------------------------------
*
* PNG_DecompressBGRX --
*
* This is a very simple PNG decompressor, which requires very
* little code and no separate temporary buffers.
*
* Limitations:
*
* - Modifies the input PNG file, in order to join all IDAT chunks.
*
* - Only supports RGB (24-bit) PNG files.
*
* - Always writes the output in 32-bit BGRX format.
*
* - Does not support interlaced PNGs.
*
* - Does not support filter types other than "None", "Sub", and "Up".
*
* - Has no validation whatsoever. Unexpected or broken PNGs
* will just cause us to produce incorrect results or crash.
*
* - Uses one extra line ('pitch' bytes) of memory in 'framebuffer'
* past the end of the decompressed image, for scratch space.
*
* Results:
* Writes the decompressed image to 'framebuffer'.
*
* Side effects:
* Uses the framebuffer as temporary space during decompression.
* Modifies the input PNG data in a non-reversible way.
*
*----------------------------------------------------------------------
*/
void
PNG_DecompressBGRX(PNGChunkIHDR *ihdr, // IN
uint32 *framebuffer, // OUT
uint32 pitch) // OUT
{
uint32 width = bswap32(ihdr->width);
uint32 height = bswap32(ihdr->height);
/*
* Size of raw decompressed image: 3 bytes per pixel, plus one byte
* (filter type) per scanline.
*/
uint32 rawPitch = (width * 3) + 1;
unsigned long rawSize = height * rawPitch;
/*
* Size of final decompressed image
*/
uint32 finalSize = height * pitch;
/*
* Use the bottom of the framebuffer for temporary memory. The
* final raw-to-final conversion must read from higher addresses
* and write to lower addresses, so we don't overwrite our
* temporary buffer prematurely. To do this with all filter types,
* we need to use one extra line of temporary space.
*/
uint8 *rawBuffer = (uint8*)framebuffer + finalSize - rawSize + pitch;
/*
* Decompress all IDAT data into our raw buffer. We need to join
* all IDAT chunks to get a raw zlib data stream, then strip off
* the 2-byte ZLIB header and 4-byte checksum to get a raw DEFLATE
* stream.
*/
PNGChunk *idat = PNGJoinIDAT(&ihdr->hdr);
unsigned long compressedSize = bswap32(idat->length) - 6;
puff(rawBuffer, &rawSize, idat->data + 2, &compressedSize);
/*
* Line-by-line, expand the decompressed filtered data into BGRX.
*/
uint32 lines = height;
Bool notFirstRow = FALSE;
while (lines--) {
uint8 *rawLine = rawBuffer;
uint32 *fbLine = framebuffer;
uint32 pixels = width;
framebuffer = (uint32*) (pitch + (uint8*)framebuffer);
rawBuffer += rawPitch;
uint8 filterType = *(rawLine++);
Bool notFirstColumn = FALSE;
while (pixels--) {
/*
* Undo the per-scanline filtering.
*/
uint8 *up = rawLine - rawPitch;
uint8 *left = rawLine - 3;
uint8 *upLeft = rawLine - 3 - rawPitch;
uint32 i;
for (i = 0; i < 3; i++) {
switch (filterType) {
case 0: // None
break;
case 1: // Sub
if (notFirstColumn) {
rawLine[i] += left[i];
}
break;
case 2: // Up
if (notFirstRow) {
rawLine[i] += up[i];
}
break;
case 3: // Average
rawLine[i] += ((notFirstColumn ? left[i] : 0) +
(notFirstRow ? up[i] : 0)) >> 1;
break;
case 4: // Paeth
rawLine[i] += PaethPredictor(notFirstColumn ? left[i] : 0,
notFirstRow ? up[i] : 0,
(notFirstRow && notFirstColumn)
? upLeft[i] : 0);
break;
}
}
/*
* Decode RGB to BGRX.
*/
uint8 r = rawLine[0];
uint8 g = rawLine[1];
uint8 b = rawLine[2];
*(fbLine++) = (r << 16) | (g << 8) | b;
rawLine += 3;
notFirstColumn = TRUE;
}
notFirstRow = TRUE;
}
}