Skip to content

Commit

Permalink
Merge f492ec6 into 968c2cf
Browse files Browse the repository at this point in the history
  • Loading branch information
danpla committed Jun 15, 2018
2 parents 968c2cf + f492ec6 commit f652962
Show file tree
Hide file tree
Showing 38 changed files with 286 additions and 6 deletions.
Binary file added Tests/images/tga/common/1x1_l.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Tests/images/tga/common/1x1_l_bl_raw.tga
Binary file not shown.
Binary file added Tests/images/tga/common/1x1_l_bl_rle.tga
Binary file not shown.
Binary file added Tests/images/tga/common/1x1_l_tl_raw.tga
Binary file not shown.
Binary file added Tests/images/tga/common/1x1_l_tl_rle.tga
Binary file not shown.
Binary file added Tests/images/tga/common/200x32_l.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Tests/images/tga/common/200x32_l_bl_raw.tga
Binary file not shown.
Binary file added Tests/images/tga/common/200x32_l_bl_rle.tga
Binary file not shown.
Binary file added Tests/images/tga/common/200x32_l_tl_raw.tga
Binary file not shown.
Binary file added Tests/images/tga/common/200x32_l_tl_rle.tga
Binary file not shown.
Binary file added Tests/images/tga/common/200x32_la.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Tests/images/tga/common/200x32_la_bl_raw.tga
Binary file not shown.
Binary file added Tests/images/tga/common/200x32_la_bl_rle.tga
Binary file not shown.
Binary file added Tests/images/tga/common/200x32_la_tl_raw.tga
Binary file not shown.
Binary file added Tests/images/tga/common/200x32_la_tl_rle.tga
Binary file not shown.
Binary file added Tests/images/tga/common/200x32_p.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Tests/images/tga/common/200x32_p_bl_raw.tga
Binary file not shown.
Binary file added Tests/images/tga/common/200x32_p_bl_rle.tga
Binary file not shown.
Binary file added Tests/images/tga/common/200x32_p_tl_raw.tga
Binary file not shown.
Binary file added Tests/images/tga/common/200x32_p_tl_rle.tga
Binary file not shown.
Binary file added Tests/images/tga/common/200x32_rgb.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Tests/images/tga/common/200x32_rgb_bl_raw.tga
Binary file not shown.
Binary file added Tests/images/tga/common/200x32_rgb_bl_rle.tga
Binary file not shown.
Binary file added Tests/images/tga/common/200x32_rgb_tl_raw.tga
Binary file not shown.
Binary file added Tests/images/tga/common/200x32_rgb_tl_rle.tga
Binary file not shown.
Binary file added Tests/images/tga/common/200x32_rgba.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Tests/images/tga/common/200x32_rgba_bl_raw.tga
Binary file not shown.
Binary file added Tests/images/tga/common/200x32_rgba_bl_rle.tga
Binary file not shown.
Binary file added Tests/images/tga/common/200x32_rgba_tl_raw.tga
Binary file not shown.
Binary file added Tests/images/tga/common/200x32_rgba_tl_rle.tga
Binary file not shown.
12 changes: 12 additions & 0 deletions Tests/images/tga/common/readme.txt
@@ -0,0 +1,12 @@
Images in this directory was created with GIMP.

TGAs have names in the following format:

{width}x{height}_{mode}_{origin}_{compression}.tga

Where:
mode is PIL mode in lower case (L, P, RGB, etc.)
origin:
"bl" - bottom left
"tl" - top left
compression is either "raw" or "rle"
67 changes: 67 additions & 0 deletions Tests/test_file_tga.py
@@ -1,10 +1,77 @@
import os
from glob import glob
from itertools import product

from helper import unittest, PillowTestCase

from PIL import Image


_TGA_DIR = os.path.join("Tests", "images", "tga")
_TGA_DIR_COMMON = os.path.join(_TGA_DIR, "common")


class TestFileTga(PillowTestCase):

_MODES = ("L", "P", "RGB", "RGBA")
_ORIGINS = ("tl", "bl")
_COMPRESSION = ("raw", "rle")

_ORIGIN_TO_ORIENTATION = {
"tl": 1,
"bl": -1
}

def test_sanity(self):
for mode in self._MODES:
png_paths = glob(
os.path.join(
_TGA_DIR_COMMON, "*x*_{}.png".format(mode.lower())))

for png_path in png_paths:
reference_im = Image.open(png_path)
self.assertEqual(reference_im.mode, mode)

path_no_ext = os.path.splitext(png_path)[0]
for origin, rle in product(self._ORIGINS, (True, False)):
tga_path = "{}_{}_{}.tga".format(
path_no_ext, origin, "rle" if rle else "raw")

original_im = Image.open(tga_path)
if rle:
self.assertEqual(
original_im.info["compression"], "tga_rle")
self.assertEqual(
original_im.info["orientation"],
self._ORIGIN_TO_ORIENTATION[origin])
if mode == "P":
self.assertEqual(
original_im.getpalette(),
reference_im.getpalette())

self.assert_image_equal(original_im, reference_im)

# Generate a new test name every time so the
# test will not fail with permission error
# on Windows.
test_file = self.tempfile("temp.tga")

original_im.save(test_file, rle=rle)
saved_im = Image.open(test_file)
if rle:
self.assertEqual(
saved_im.info["compression"],
original_im.info["compression"])
self.assertEqual(
saved_im.info["orientation"],
original_im.info["orientation"])
if mode == "P":
self.assertEqual(
saved_im.getpalette(),
original_im.getpalette())

self.assert_image_equal(saved_im, original_im)

def test_id_field(self):
# tga file with id field
test_file = "Tests/images/tga_id_field.tga"
Expand Down
8 changes: 4 additions & 4 deletions setup.py
Expand Up @@ -46,10 +46,10 @@
"Negative", "Offset", "Pack", "PackDecode", "Palette", "Paste", "Quant",
"QuantOctree", "QuantHash", "QuantHeap", "PcdDecode", "PcxDecode",
"PcxEncode", "Point", "RankFilter", "RawDecode", "RawEncode", "Storage",
"SgiRleDecode", "SunRleDecode", "TgaRleDecode", "Unpack", "UnpackYCC",
"UnsharpMask", "XbmDecode", "XbmEncode", "ZipDecode", "ZipEncode",
"TiffDecode", "Jpeg2KDecode", "Jpeg2KEncode", "BoxBlur", "QuantPngQuant",
"codec_fd")
"SgiRleDecode", "SunRleDecode", "TgaRleDecode", "TgaRleEncode", "Unpack",
"UnpackYCC", "UnsharpMask", "XbmDecode", "XbmEncode", "ZipDecode",
"ZipEncode", "TiffDecode", "Jpeg2KDecode", "Jpeg2KEncode", "BoxBlur",
"QuantPngQuant", "codec_fd")

DEBUG = False

Expand Down
15 changes: 13 additions & 2 deletions src/PIL/TgaImagePlugin.py
Expand Up @@ -147,6 +147,11 @@ def _save(im, fp, filename):
except KeyError:
raise IOError("cannot write mode %s as TGA" % im.mode)

rle = im.encoderinfo.get("rle", False)

if rle:
imagetype += 8

if colormaptype:
colormapfirst, colormaplength, colormapentry = 0, 256, 24
else:
Expand Down Expand Up @@ -177,8 +182,14 @@ def _save(im, fp, filename):
if colormaptype:
fp.write(im.im.getpalette("RGB", "BGR"))

ImageFile._save(
im, fp, [("raw", (0, 0) + im.size, 0, (rawmode, 0, orientation))])
if rle:
ImageFile._save(
im,
fp,
[("tga_rle", (0, 0) + im.size, 0, (rawmode, orientation))])
else:
ImageFile._save(
im, fp, [("raw", (0, 0) + im.size, 0, (rawmode, 0, orientation))])

# write targa version 2 footer
fp.write(b"\000" * 8 + b"TRUEVISION-XFILE." + b"\000")
Expand Down
2 changes: 2 additions & 0 deletions src/_imaging.c
Expand Up @@ -3594,6 +3594,7 @@ extern PyObject* PyImaging_JpegEncoderNew(PyObject* self, PyObject* args);
extern PyObject* PyImaging_Jpeg2KEncoderNew(PyObject* self, PyObject* args);
extern PyObject* PyImaging_PcxEncoderNew(PyObject* self, PyObject* args);
extern PyObject* PyImaging_RawEncoderNew(PyObject* self, PyObject* args);
extern PyObject* PyImaging_TgaRleEncoderNew(PyObject* self, PyObject* args);
extern PyObject* PyImaging_XbmEncoderNew(PyObject* self, PyObject* args);
extern PyObject* PyImaging_ZipEncoderNew(PyObject* self, PyObject* args);
extern PyObject* PyImaging_LibTiffEncoderNew(PyObject* self, PyObject* args);
Expand Down Expand Up @@ -3661,6 +3662,7 @@ static PyMethodDef functions[] = {
{"sgi_rle_decoder", (PyCFunction)PyImaging_SgiRleDecoderNew, 1},
{"sun_rle_decoder", (PyCFunction)PyImaging_SunRleDecoderNew, 1},
{"tga_rle_decoder", (PyCFunction)PyImaging_TgaRleDecoderNew, 1},
{"tga_rle_encoder", (PyCFunction)PyImaging_TgaRleEncoderNew, 1},
{"xbm_decoder", (PyCFunction)PyImaging_XbmDecoderNew, 1},
{"xbm_encoder", (PyCFunction)PyImaging_XbmEncoderNew, 1},
#ifdef HAVE_LIBZ
Expand Down
33 changes: 33 additions & 0 deletions src/encode.c
Expand Up @@ -492,6 +492,39 @@ PyImaging_RawEncoderNew(PyObject* self, PyObject* args)
}


/* -------------------------------------------------------------------- */
/* TGA */
/* -------------------------------------------------------------------- */

PyObject*
PyImaging_TgaRleEncoderNew(PyObject* self, PyObject* args)
{
ImagingEncoderObject* encoder;

char *mode;
char *rawmode;
int ystep = 1;
/*int depth = 8;*/

if (!PyArg_ParseTuple(args, "ss|i", &mode, &rawmode, &ystep))
return NULL;

encoder = PyImaging_EncoderNew(0);
if (encoder == NULL)
return NULL;

if (get_packer(encoder, mode, rawmode) < 0)
return NULL;

encoder->encode = ImagingTgaRleEncode;

encoder->state.ystep = ystep;

return (PyObject*) encoder;
}



/* -------------------------------------------------------------------- */
/* XBM */
/* -------------------------------------------------------------------- */
Expand Down
2 changes: 2 additions & 0 deletions src/libImaging/Imaging.h
Expand Up @@ -473,6 +473,8 @@ extern int ImagingSunRleDecode(Imaging im, ImagingCodecState state,
UINT8* buffer, int bytes);
extern int ImagingTgaRleDecode(Imaging im, ImagingCodecState state,
UINT8* buffer, int bytes);
extern int ImagingTgaRleEncode(Imaging im, ImagingCodecState state,
UINT8* buffer, int bytes);
extern int ImagingXbmDecode(Imaging im, ImagingCodecState state,
UINT8* buffer, int bytes);
extern int ImagingXbmEncode(Imaging im, ImagingCodecState state,
Expand Down
153 changes: 153 additions & 0 deletions src/libImaging/TgaRleEncode.c
@@ -0,0 +1,153 @@

#include "Imaging.h"

#include <assert.h>
#include <string.h>


static int comparePixels(const UINT8* buf, int x, int bytesPerPixel)
{
buf += x * bytesPerPixel;
return memcmp(buf, buf + bytesPerPixel, bytesPerPixel) == 0;
}


int
ImagingTgaRleEncode(Imaging im, ImagingCodecState state, UINT8* buf, int bytes)
{
UINT8* dst;
int bytesPerPixel;

if (state->state == 0) {
if (state->ystep < 0) {
state->ystep = -1;
state->y = state->ysize - 1;
} else
state->ystep = 1;

state->state = 1;
}

dst = buf;
bytesPerPixel = (state->bits + 7) / 8;

while (1) {
int flushCount;

/*
* state->count is the numbers of bytes in the packet,
* excluding the 1-byte descriptor.
*/
if (state->count == 0) {
UINT8* row;
UINT8 descriptor;
int startX;

assert(state->x <= state->xsize);

/* Make sure we have space for the descriptor. */
if (bytes < 1)
break;

if (state->x == state->xsize) {
state->x = 0;

state->y += state->ystep;
if (state->y < 0 || state->y >= state->ysize) {
state->errcode = IMAGING_CODEC_END;
break;
}
}

if (state->x == 0)
state->shuffle(
state->buffer,
(UINT8*)im->image[state->y + state->yoff]
+ state->xoff * im->pixelsize,
state->xsize);

row = state->buffer;

/* Start with a raw packet for 1 px. */
descriptor = 0;
startX = state->x;
state->count = bytesPerPixel;

if (state->x + 1 < state->xsize) {
int maxLookup;
int isRaw;

isRaw = !comparePixels(row, state->x, bytesPerPixel);
++state->x;

/*
* A packet can contain up to 128 pixels;
* 2 are already behind (state->x points to
* the second one).
*/
maxLookup = state->x + 126;
/* A packet must not span multiple rows. */
if (maxLookup > state->xsize - 1)
maxLookup = state->xsize - 1;

if (isRaw) {
while (state->x < maxLookup)
if (!comparePixels(row, state->x, bytesPerPixel))
++state->x;
else {
/* Two identical pixels will go to RLE packet. */
--state->x;
break;
}

state->count += (state->x - startX) * bytesPerPixel;
} else {
descriptor |= 0x80;

while (state->x < maxLookup)
if (comparePixels(row, state->x, bytesPerPixel))
++state->x;
else
break;
}
}

/*
* state->x currently points to the last pixel to be
* included in the packet. The pixel count in the
* descriptor is 1 less than actual number of pixels in
* the packet, that is, state->x == startX if we encode
* only 1 pixel.
*/
descriptor += state->x - startX;
*dst++ = descriptor;
--bytes;

/* Advance to past-the-last encoded pixel. */
++state->x;
}

assert(bytes >= 0);
assert(state->count > 0);
assert(state->x > 0);
assert(state->count <= state->x * bytesPerPixel);

if (bytes == 0)
break;

flushCount = state->count;
if (flushCount > bytes)
flushCount = bytes;

memcpy(
dst,
state->buffer + (state->x * bytesPerPixel - state->count),
flushCount);
dst += flushCount;
bytes -= flushCount;

state->count -= flushCount;
}

return dst - buf;
}

0 comments on commit f652962

Please sign in to comment.