From ccd9d4b489e0360968b95d362b032f1b295d4686 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Fri, 8 Jan 2016 07:18:05 -0800 Subject: [PATCH 1/4] Test images for GbrImagePlugin, created in GIMP 2.8 by Eric Soroos --- Tests/images/gbr.gbr | Bin 0 -> 16423 bytes Tests/images/gbr.png | Bin 0 -> 2014 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 Tests/images/gbr.gbr create mode 100644 Tests/images/gbr.png diff --git a/Tests/images/gbr.gbr b/Tests/images/gbr.gbr new file mode 100644 index 0000000000000000000000000000000000000000..054b82df5ba930779dfa3e8bad304d171bb74e6b GIT binary patch literal 16423 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 Date: Fri, 8 Jan 2016 07:18:48 -0800 Subject: [PATCH 2/4] Working GbrImagePlugin for version 2 GBR (gimp brush) files --- PIL/GbrImagePlugin.py | 57 +++++++++++++++++++++++++++++------------- Tests/test_file_gbr.py | 9 ++++++- 2 files changed, 48 insertions(+), 18 deletions(-) diff --git a/PIL/GbrImagePlugin.py b/PIL/GbrImagePlugin.py index 15282ecee..8edb8f487 100644 --- a/PIL/GbrImagePlugin.py +++ b/PIL/GbrImagePlugin.py @@ -1,17 +1,28 @@ # # The Python Imaging Library -# $Id$ # # load a GIMP brush file # # History: # 96-03-14 fl Created +# 16-01-08 es Version 2 # # Copyright (c) Secret Labs AB 1997. # Copyright (c) Fredrik Lundh 1996. +# Copyright (c) Eric Soroos 2016. # # See the README file for information on usage and redistribution. # +# +# See https://github.com/GNOME/gimp/blob/master/devel-docs/gbr.txt for +# format documentation. +# +# This code Interprets version 1 and 2 .gbr files. +# Version 1 files are obsolete, and should not be used for new +# brushes. +# Version 2 files are saved by GIMP v2.8 (at least) +# Version 3 files have a format specifier of 18 for 16bit floats in +# the color depth field. This is currently unsupported by Pillow. from PIL import Image, ImageFile, _binary @@ -19,7 +30,7 @@ i32 = _binary.i32be def _accept(prefix): - return len(prefix) >= 8 and i32(prefix) >= 20 and i32(prefix[4:8]) == 1 + return len(prefix) >= 8 and i32(prefix[:4]) >= 20 and i32(prefix[4:8]) in (1,2) ## @@ -31,41 +42,53 @@ class GbrImageFile(ImageFile.ImageFile): format_description = "GIMP brush file" def _open(self): - header_size = i32(self.fp.read(4)) version = i32(self.fp.read(4)) - if header_size < 20 or version != 1: + if header_size < 20: raise SyntaxError("not a GIMP brush") + if version not in (1,2): + raise SyntaxError("Unsupported GIMP brush version: %s" %version) width = i32(self.fp.read(4)) height = i32(self.fp.read(4)) color_depth = i32(self.fp.read(4)) - if width <= 0 or height <= 0 or color_depth != 1: + if width <= 0 or height <= 0: raise SyntaxError("not a GIMP brush") + if color_depth not in (1,4): + raise SyntaxError("Unsupported GMP brush color depth: %s" %color_depth) + + if version == 1: + comment_length = header_size-20 + else: + comment_length = header_size-28 + magic_number = self.fp.read(4) + if magic_number != b'GIMP': + raise SyntaxError("not a GIMP brush, bad magic number") + self.info['spacing'] = i32(self.fp.read(4)) - comment = self.fp.read(header_size - 20)[:-1] + comment = self.fp.read(comment_length)[:-1] - self.mode = "L" + if color_depth == 1: + self.mode = "L" + else: + self.mode = 'RGBA' + self.size = width, height self.info["comment"] = comment - # Since the brush is so small, we read the data immediately - self.data = self.fp.read(width * height) + # Image might not be small + Image._decompression_bomb_check(self.size) + + # Data is an uncompressed block of w * h * bytes/pixel + self._data_size = width * height * color_depth def load(self): - - if not self.data: - return - - # create an image out of the brush data block self.im = Image.core.new(self.mode, self.size) - self.im.frombytes(self.data) - self.data = b"" + self.frombytes(self.fp.read(self._data_size)) # # registry Image.register_open(GbrImageFile.format, GbrImageFile, _accept) - Image.register_extension(GbrImageFile.format, ".gbr") diff --git a/Tests/test_file_gbr.py b/Tests/test_file_gbr.py index 57b301ada..d38b4a70f 100644 --- a/Tests/test_file_gbr.py +++ b/Tests/test_file_gbr.py @@ -1,6 +1,6 @@ from helper import unittest, PillowTestCase -from PIL import GbrImagePlugin +from PIL import Image, GbrImagePlugin class TestFileGbr(PillowTestCase): @@ -12,6 +12,13 @@ class TestFileGbr(PillowTestCase): lambda: GbrImagePlugin.GbrImageFile(invalid_file)) + def test_gbr_file(self): + im = Image.open('Tests/images/gbr.gbr') + + target = Image.open('Tests/images/gbr.png') + + self.assert_image_equal(target, im) + if __name__ == '__main__': unittest.main() From 00dc65782488f04e6fe419a4c600e3142b7673da Mon Sep 17 00:00:00 2001 From: wiredfool Date: Fri, 8 Jan 2016 07:25:42 -0800 Subject: [PATCH 3/4] Documentation for GBR --- docs/handbook/image-file-formats.rst | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/docs/handbook/image-file-formats.rst b/docs/handbook/image-file-formats.rst index 2b728deef..ce5382fe1 100644 --- a/docs/handbook/image-file-formats.rst +++ b/docs/handbook/image-file-formats.rst @@ -660,14 +660,17 @@ into account. GBR ^^^ -The GBR decoder reads GIMP brush files. +The GBR decoder reads GIMP brush files, version 1 and 2. The :py:meth:`~PIL.Image.Image.open` method sets the following :py:attr:`~PIL.Image.Image.info` properties: -**description** +**comment** The brush name. +**spacing** + The spacing between the brushes, in pixels. Version 2 only. + GD ^^ From d047349958161cce42ef370717ba77ace749e8a2 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Fri, 8 Jan 2016 10:15:06 -0800 Subject: [PATCH 4/4] YAFile format --- MANIFEST.in | 1 + 1 file changed, 1 insertion(+) diff --git a/MANIFEST.in b/MANIFEST.in index 4fd9c602e..02ee5278b 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -27,6 +27,7 @@ recursive-include Tests *.dcx recursive-include Tests *.doc recursive-include Tests *.eps recursive-include Tests *.fli +recursive-include Tests *.gbr recursive-include Tests *.ggr recursive-include Tests *.gif recursive-include Tests *.gpl