r32951 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r32950‎ | r32951 | r32952 >
Date:09:45, 8 April 2008
Author:btongminh
Status:old
Tags:
Comment:
Adding (unfinished) tool to resize PNGs in a memory friendly way
Modified paths:
  • /trunk/pngds/README.txt (added) (history)
  • /trunk/pngds/pngreader.c (added) (history)
  • /trunk/pngds/pngreader.h (added) (history)
  • /trunk/pngds/pngreader.py (added) (history)
  • /trunk/pngds/pngresizer.py (added) (history)

Diff [purge]

Index: trunk/pngds/pngresizer.py
@@ -0,0 +1,74 @@
 2+import math
 3+
 4+from pngreader import *
 5+
 6+class PNGResizer(PNGReader):
 7+ def __init__(self, stream, width, height, out = None):
 8+ PNGReader.__init__(self, stream)
 9+ self.resize_width = width
 10+ self.resize_height = height
 11+
 12+ self.current_scanline = 0
 13+ self.written_lines = 0
 14+ self.scanlines = []
 15+ self.last_line = ''
 16+
 17+ if out:
 18+ self.out = out
 19+ else:
 20+ self.out = StringIO()
 21+
 22+ def read_chunk(self):
 23+ has_data = PNGReader.read_chunk(self)
 24+ if not has_data:
 25+ while self.written_lines < self.resize_height:
 26+ self.out.write(self.last_line)
 27+ self.written_lines += 1
 28+ return has_data
 29+
 30+ def read_header(self, length):
 31+ PNGReader.read_header(self, length)
 32+
 33+ self.fx = float(self.width) / self.resize_width
 34+ self.fy = float(self.height) / self.resize_height
 35+ if self.fx < 1.0 or self.fy < 1.0:
 36+ raise ValueError('Upsampling unsupported')
 37+
 38+ def completed_scanline(self):
 39+ pixels = tuple(self.read_scanline())
 40+
 41+ current_scanline = []
 42+ for i in xrange(self.resize_width):
 43+ start = int(math.ceil(self.fx * i))
 44+ end = int(math.ceil(self.fx * (i + 1)))
 45+ divisor = end - start
 46+
 47+ new_pixel = [0] * COLOR_DEPTH[self.colortype]
 48+ for j in xrange(len(new_pixel)):
 49+ for k in xrange(end - start):
 50+ new_pixel[j] += ord(pixels[start + k][j]) / divisor
 51+ new_pixel[j] = chr(new_pixel[j])
 52+
 53+ #current_scanline.append(''.join(new_pixel))
 54+ current_scanline.extend(new_pixel)
 55+
 56+ self.scanlines.append(current_scanline)
 57+ self.current_scanline += 1
 58+ if (self.current_scanline / self.fy) > (self.written_lines + 1):
 59+ line = [0] * len(current_scanline)
 60+ for i in xrange(len(line)):
 61+ for j in xrange(len(self.scanlines)):
 62+ line[i] += ord(self.scanlines[j][i]) / len(self.scanlines)
 63+ line[i] = chr(line[i])
 64+ self.last_line = ''.join(line)
 65+
 66+ self.out.write(self.last_line)
 67+ self.scanlines = []
 68+ self.written_lines += 1
 69+
 70+def test(filename):
 71+ reader = PNGResizer(open(filename, 'rb'), 32, 32)
 72+ reader.parse()
 73+ reader.scanline = reader.previous_scanline = None
 74+ print reader.__dict__
 75+ return reader
Property changes on: trunk/pngds/pngresizer.py
___________________________________________________________________
Added: svn:eol-style
176 + native
Index: trunk/pngds/pngreader.c
@@ -0,0 +1,232 @@
 2+#include "zlib.h"
 3+
 4+#include "pngreader.h"
 5+
 6+#define BUFFER_IN_SIZE 32768
 7+#define BUFFER_OUT_SIZE 65536
 8+
 9+void png_resize(FILE* fin, FILE* fout, unsigned int width, unsigned int height)
 10+{
 11+ pngreader info;
 12+
 13+ info.width = width;
 14+ info.height = height;
 15+ info.fin = fin;
 16+ info.fout = fout;
 17+
 18+ char header[8];
 19+ fread(header, 1, 8, fin);
 20+ if (strcmp(header, "\x89PNG\r\n\x1a\n", 8))
 21+ png_die("header", header, 8);
 22+
 23+ while (png_read_chunk(&info));
 24+}
 25+
 26+int png_die(char *msg, void *data, int data_len)
 27+{
 28+ fwrite(msg, 1, strlen(msg), stderr);
 29+ exit(1);
 30+}
 31+
 32+int png_read_chunk(pngresize *info)
 33+{
 34+ chunkheader c_head;
 35+ fread(&c_head, 4, 2, info->fin);
 36+
 37+ if (strncmp(c_head.type, "IHDR", 4) == 0)
 38+ {
 39+ png_read_header(info, c_head.length);
 40+ }
 41+ else if (strncmp(c_head.type, "PLTE", 4) == 0)
 42+ {
 43+ png_read_palette(info, c_head.length);
 44+ }
 45+ else if (strncmp(c_head.type, "IDAT", 4) == 0)
 46+ {
 47+ png_read_data(info, c_head.length);
 48+ }
 49+ else if (c_head.type[0] & 32)
 50+ {
 51+ png_read_ancillary(info, c_head.length);
 52+ }
 53+ else if (strncmp(c_head.type, "IEND", 4) != 0)
 54+ {
 55+ png_die("critical_chunk", c_head.type, 4);
 56+ }
 57+
 58+ unsigned int crc;
 59+ fread(&crc, 4, 1, info->fin);
 60+
 61+ return strncmp(c_head.type, "IEND", 42);
 62+}
 63+
 64+void png_read_header(pngreader *info, int length)
 65+{
 66+ info->header = malloc(sizeof(pngheader));
 67+ fread(info->header, sizeof(pngheader), 1, info->fin);
 68+
 69+ if (info->header->compression != COMPRESS_DEFLATE)
 70+ png_die("unknown_compression", info->header->compression, 1);
 71+ if (info->header->filter_method != FILTER_METHOD_BASIC_ADAPTIVE)
 72+ png_die("unknown_filter_method", info->header->filter_method, 1);
 73+ if (info->header->interlace)
 74+ png_die("interlace_unsupported", NULL, 0);
 75+
 76+ info->bytedepth = info->header->bpp / 8;
 77+ if (info->header->bitdepth % 8) info->bytedepth++;
 78+
 79+ info->bpp = info->bytedepth;
 80+ switch (info->header->colortype)
 81+ {
 82+ case COLOR_GRAY:
 83+ case COLOR_PALETTE:
 84+ info->bpp *= 1;
 85+ break;
 86+ case COLOR_GRAYA:
 87+ info->bpp *= 2;
 88+ break;
 89+ case COLOR_RGB:
 90+ info->bpp *= 3;
 91+ break;
 92+ case COLOR_RGBA:
 93+ info->bpp *= 4;
 94+ break;
 95+ default:
 96+ png_die("unknown_colortype", info->header->colortype, 1);
 97+ }
 98+ info->expect_filter = 0;
 99+ info->previous_scanline = malloc(info->header->width * info->bpp);
 100+ memset(info->previous_scanline, 0, info->header->width * info->bpp);
 101+ info->current_scanline = malloc(info->header->width * info->bpp);
 102+
 103+ info->zst.zalloc = (alloc_func)NULL;
 104+ info->zst.zfree = (free_func)Z_NULL;
 105+
 106+ inflateInit(&info->zst);
 107+
 108+ info->zst.next_in = malloc(BUFFER_IN_SIZE);
 109+ info->zst.next_out = malloc(BUFFER_OUT_SIZE);
 110+}
 111+
 112+void png_read_palette(pngreader *info, int length)
 113+{
 114+ if (length % 3)
 115+ png_die("malformed_palette_length", length);
 116+
 117+ info->palette = malloc(sizeof(*rgbcolor) * length / 3);
 118+ for (int i = 0; i < length; i += 3)
 119+ {
 120+ *(info->palette + i / 3) = malloc(sizeof(color));
 121+ fread(*(info->palette + i / 3), 3, 1, info->fin);
 122+ }
 123+}
 124+
 125+void png_read_data(pngreader *info, int length)
 126+{
 127+ // Does not work, need to find out how the buffers work
 128+ while (length)
 129+ {
 130+ int size = min(length, BUFFER_IN_SIZE);
 131+ info->zst.next_in = malloc(size);
 132+ info->zst.avail_in = size;
 133+ fread(info->zst.next_in, 1, size, info->fin);
 134+ length -= size;
 135+
 136+ int err = inflate(info->zst, Z_SYNC_FLUSH);
 137+ switch (err)
 138+ {
 139+ case (Z_STREAM_END):
 140+ if (size)
 141+ die_png("premature_input_end", NULL, 0);
 142+ return;
 143+
 144+ // Code from <http://svn.python.org/view/python/trunk/Modules/zlibmodule.c?rev=61874&view=auto>
 145+ case (Z_BUF_ERROR):
 146+ /*
 147+ * If there is at least 1 byte of room according to zst.avail_out
 148+ * and we get this error, assume that it means zlib cannot
 149+ * process the inflate call() due to an error in the data.
 150+ */
 151+ if (info->zst.avail_out > 0)
 152+ die_debug("input_error", NULL, 0);
 153+ // Fall through
 154+ case (Z_OK):
 155+ png_defilter(info, info->zst.next_out,
 156+ BUFFER_OUT_SIZE - info->zst.avail_out);
 157+ default:
 158+ // Hmm...
 159+ }
 160+ }
 161+}
 162+
 163+void png_defilter(pngreader *info, unsigned char *buffer, int size)
 164+{
 165+ for (int i = 0; i < size; i++)
 166+ {
 167+ int x;
 168+ unsigned char byte = *(buffer + byte);
 169+
 170+ if (info->expect_filter)
 171+ {
 172+ info->expect_filter = 0;
 173+ info->filter = byte;
 174+ continue;
 175+ }
 176+
 177+ switch (info->filter)
 178+ {
 179+ case (FILTER_NONE):
 180+ break;
 181+ case (FILTER_SUB):
 182+ x = info->scan_pos - info->bpp;
 183+ if (x >= 0) byte += *(info->current_scanline + x);
 184+ break;
 185+ case (FILTER_UP):
 186+ byte += *(info->previous_scanline + info->scan_pos);
 187+ break;
 188+ case (FILTER_AVERAGE):
 189+ x = info->scan_pos - info->bpp;
 190+ if (x >= 0)
 191+ byte += (*(info->current_scanline + x) +
 192+ *(info->previous_scanline + info->scan_pos)) / 2;
 193+ else
 194+ byte += *(info->previous_scanline + info->scan_pos) / 2;
 195+ break;
 196+ case (FILTER_PAETH):
 197+ unsigned char a, b, c;
 198+ unsigned char pa, pb, pc;
 199+ short p;
 200+
 201+ x = info->scan_pos - info->bpp;
 202+ b = *(info->previous_scanline + info->scan_pos);
 203+ if (x >= 0)
 204+ {
 205+ a = *(info->current_scanline + x);
 206+ c = *(info->previous_scanline + x);
 207+ }
 208+ else
 209+ {
 210+ a = c = 0;
 211+ }
 212+
 213+ p = a + b - c;
 214+ pa = abs(p - a);
 215+ pb = abs(p - b);
 216+ pc = abs(p - c);
 217+
 218+ if (pa <= pb && pa <= pc) byte += a;
 219+ else if (pb <= pc) byte += b;
 220+ else byte += c;
 221+ default:
 222+ png_die("unknown_filter", &info->filter, 1);
 223+ }
 224+ *(info->scanline + i) = byte;
 225+
 226+ if ((info->scan_pos % info->bpp) == 0 &&
 227+ (info->scan_pos / info->bpp) == 0)
 228+ {
 229+ info->previous_scanline = info->current_scanline;
 230+ info->expect_filter = 1;
 231+ }
 232+ }
 233+}
\ No newline at end of file
Property changes on: trunk/pngds/pngreader.c
___________________________________________________________________
Added: svn:eol-style
1234 + native
Index: trunk/pngds/README.txt
@@ -0,0 +1,10 @@
 2+Portable Network Graphics Downsampler is a tool which allows downsizing of PNG
 3+images without loading the entire file in memory. This makes it possible to
 4+resize extremely large PNGs.
 5+
 6+The implementation is Python works and uses indeed only few memory, but is much
 7+too slow for use. This implementation also only outputs raw data and does not
 8+recompress to PNG.
 9+
 10+The C version is supposed to be faster and even less memory using, but not yet
 11+working.
\ No newline at end of file
Property changes on: trunk/pngds/README.txt
___________________________________________________________________
Added: svn:eol-style
112 + native
Index: trunk/pngds/pngreader.h
@@ -0,0 +1,81 @@
 2+#include "zlib.h"
 3+
 4+/*
 5+ * Defines
 6+ */
 7+
 8+#define COLOR_GRAY 0
 9+#define COLOR_RGB 2
 10+#define COLOR_PALETTE 3
 11+#define COLOR_GRAYA 4
 12+#define COLOR_RGBA 6
 13+
 14+#define COMPRESS_DEFLATE 0
 15+#define COMPRESS_FLAG_DEFLATE 8
 16+
 17+#define FILTER_METHOD_BASIC_ADAPTIVE 0
 18+
 19+#define FILTER_NONE 0
 20+#define FILTER_SUB 1
 21+#define FILTER_UP 2
 22+#define FILTER_AVERAGE 3
 23+#define FILTER_PAETH 4
 24+
 25+
 26+/*
 27+ * Types
 28+ */
 29+
 30+typedef struct
 31+{
 32+ unsigned int width;
 33+ unsigned int height;
 34+ unsigned char bitdepth;
 35+ unsigned char colortype;
 36+ unsigned char compression;
 37+ unsigned char filter_method;
 38+ unsigned char interlace;
 39+} pngheader;
 40+
 41+typedef struct
 42+{
 43+ unsigned int length;
 44+ char[4] type;
 45+} chunkheader;
 46+
 47+typedef struct
 48+{
 49+ unsigned char r;
 50+ unsigned char g;
 51+ unsigned char b;
 52+} rgbcolor;
 53+
 54+typedef struct
 55+{
 56+ pngheader *header;
 57+
 58+ unsigned char bytedepth;
 59+ unsigned char bpp;
 60+ rgbcolor **palette;
 61+
 62+ z_stream zst;
 63+
 64+ unsigned char expect_filter;
 65+ unsigned char filter;
 66+ int scan_pos;
 67+ unsigned char *previous_scanline;
 68+ unsigned char *current_scanline;
 69+
 70+ FILE *fin;
 71+ FILE *fout;
 72+
 73+ void *extra1;
 74+} pngreader;
 75+
 76+typedef struct
 77+{
 78+ unsigned int width;
 79+ unsigned int height;
 80+ double fx;
 81+ double fy;
 82+} pngresize;
\ No newline at end of file
Property changes on: trunk/pngds/pngreader.h
___________________________________________________________________
Added: svn:eol-style
183 + native
Index: trunk/pngds/pngreader.py
@@ -0,0 +1,183 @@
 2+"""
 3+ PNGReader - Copyright 2008 - Bryan Tong Minh
 4+ Licensed under the GNU Public License v2 or above
 5+"""
 6+
 7+import struct
 8+import zlib
 9+from cStringIO import StringIO
 10+
 11+COLOR_GRAY, COLOR_RGB, COLOR_PALETTE = (0, 2, 3)
 12+COLOR_GRAYA, COLOR_RGBA = (4, 6)
 13+COLOR_DEPTH = {
 14+ COLOR_GRAY: 1,
 15+ COLOR_RGB: 3,
 16+ COLOR_PALETTE: 1,
 17+ COLOR_GRAYA: 2,
 18+ COLOR_RGBA: 4,
 19+}
 20+
 21+COMPRESS_DEFLATE = 0
 22+COMPRESS_FLAG_DEFLATE = 8
 23+
 24+FILTER_METHOD_BASIC_ADAPTIVE = 0
 25+
 26+FILTER_NONE, FILTER_SUB, FILTER_UP = (0, 1, 2)
 27+FILTER_AVERAGE, FILTER_PAETH = (3, 4)
 28+
 29+class PNGReader(object):
 30+ def __init__(self, stream, out = None):
 31+ self.stream = stream
 32+ self.previous_scanline = None
 33+ self.scanline = []
 34+ self.expect_filter = True
 35+ self.chunks = []
 36+
 37+ if out:
 38+ self.out = out
 39+ else:
 40+ self.out = StringIO()
 41+
 42+ def parse(self):
 43+ header = self.stream.read(8)
 44+ if header != '\x89PNG\r\n\x1a\n':
 45+ raise ValueError('Invalid header', header)
 46+
 47+ while self.read_chunk():
 48+ pass
 49+ self.out.close()
 50+
 51+ def read_chunk(self):
 52+ length = struct.unpack('!L', self.stream.read(4))[0]
 53+ chunk_type = self.stream.read(4)
 54+ ancillary = ord(chunk_type[0]) & 32
 55+ private = ord(chunk_type[1]) & 32
 56+
 57+ self.chunks.append(chunk_type)
 58+
 59+ if chunk_type == 'IHDR':
 60+ self.read_header(length)
 61+ elif chunk_type == 'PLTE':
 62+ self.read_palette(length)
 63+ elif chunk_type == 'IDAT':
 64+ self.read_data(length)
 65+ elif ancillary:
 66+ self.stream.read(length)
 67+ elif chunk_type != 'IEND':
 68+ raise ValueError('Unrecognized critical chunk', chunk_type)
 69+
 70+ crc = self.stream.read(4)
 71+
 72+ if chunk_type == 'IEND':
 73+ return False
 74+ else:
 75+ return True
 76+
 77+ def read_header(self, length):
 78+ self.width, self.height = struct.unpack('!LL', self.stream.read(8))
 79+ self.bitdepth, self.colortype = struct.unpack('!BB', self.stream.read(2))
 80+
 81+ self.compression, self.filter_method, self.interlace = struct.unpack('!BBB', self.stream.read(3))
 82+
 83+ if self.compression != COMPRESS_DEFLATE:
 84+ raise ValueError('Unknown compressor', self.compression)
 85+ if self.filter_method != FILTER_METHOD_BASIC_ADAPTIVE:
 86+ raise ValueError('Unknown filter method', self.filtermethod)
 87+ if self.interlace:
 88+ raise ValueError('Interlacing is unsupported')
 89+
 90+ self.decompress = zlib.decompressobj()
 91+
 92+ self.bytedepth = self.bitdepth / 8
 93+ if self.bitdepth % 8: self.bytedepth += 1
 94+ self.bpp = COLOR_DEPTH[self.colortype] * self.bytedepth
 95+
 96+ self.previous_scanline = [0] * self.bpp * self.width
 97+
 98+ def read_palette(self, length):
 99+ self.palette = []
 100+ for i in xrange(length / 3):
 101+ self.palette.append(struct.unpack('!BBB', self.stream.read(3)))
 102+
 103+ def read_data(self, length):
 104+ left = length
 105+ while left:
 106+ size = min(4096, left)
 107+ data = self.stream.read(size)
 108+ left -= size
 109+
 110+ self.defilter(self.decompress.decompress(data))
 111+
 112+ def defilter(self, data):
 113+ for byte in data:
 114+ byte = ord(byte)
 115+ if self.expect_filter:
 116+ self.filter = byte
 117+ self.expect_filter = False
 118+ continue
 119+
 120+ if self.filter == FILTER_NONE:
 121+ pass
 122+ elif self.filter == FILTER_SUB:
 123+ i = len(self.scanline) - self.bpp
 124+ if i >= 0: byte += self.scanline[i]
 125+ elif self.filter == FILTER_UP:
 126+ i = len(self.scanline)
 127+ byte += self.previous_scanline[i]
 128+ elif self.filter == FILTER_AVERAGE:
 129+ i = len(self.scanline) - self.bpp
 130+ j = len(self.scanline)
 131+ if i >= 0:
 132+ byte += (self.scanline[i] +
 133+ self.previous_scanline[j]) / 2
 134+ else:
 135+ byte += self.previous_scanline[j] / 2
 136+ elif self.filter == FILTER_PAETH:
 137+ i = len(self.scanline) - self.bpp
 138+ j = len(self.scanline)
 139+ b = self.previous_scanline[j]
 140+ if i >= 0:
 141+ a = self.scanline[i]
 142+ c = self.previous_scanline[i]
 143+ else:
 144+ a = c = 0
 145+ p = a + b - c
 146+ pa = abs(p - a)
 147+ pb = abs(p - b)
 148+ pc = abs(p - c)
 149+ if pa <= pb and pa <= pc: byte += a
 150+ elif pb <= pc: byte += b
 151+ else: byte += c
 152+ else:
 153+ raise ValueError('Unknown filter', self.filter)
 154+
 155+ byte %= 256
 156+ self.scanline.append(byte)
 157+
 158+ if (len(self.scanline) % self.bpp == 0) and \
 159+ (len(self.scanline) / self.bpp) == self.width:
 160+ self.previous_scanline = self.scanline
 161+ self.completed_scanline()
 162+ self.scanline = []
 163+ self.expect_filter = filter
 164+
 165+ def read_scanline(self):
 166+ scanline = self.scanline[:]
 167+ while scanline:
 168+ yield [''.join((chr(scanline.pop(0)) for i in xrange(self.bytedepth)))
 169+ for j in xrange(COLOR_DEPTH[self.colortype])]
 170+
 171+ def completed_scanline(self):
 172+ # Override this in a subclass
 173+ for pixel in self.read_scanline():
 174+ self.out.write(''.join(pixel))
 175+
 176+
 177+def test(filename):
 178+ reader = PNGReader(open(filename, 'rb'))
 179+ try:
 180+ reader.parse()
 181+ reader.scanline = reader.previous_scanline = None
 182+ print reader.__dict__
 183+ finally:
 184+ return reader
\ No newline at end of file
Property changes on: trunk/pngds/pngreader.py
___________________________________________________________________
Added: svn:eol-style
1185 + native

Status & tagging log