From c34b6f757fc385d99086bdd755f74ceabf613454 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Thu, 21 Oct 2021 11:45:11 -0500 Subject: [PATCH 01/10] Implement gifio.GifWriter This involves: * Adding a new "L8" colorspace for colorconverters * factoring out displayio_colorconverter_convert_pixel * Making a minimal "colorspace only" version of displayio for the unix port (testing purposes) * fixing an error message I only tested writing B&W animated images, with the following script: ```python import displayio import gifio with gifio.GifWriter("foo.gif", 64, 64, displayio.Colorspace.L8) as g: for i in range(0, 256, 14): data = bytes([i, 255-i] * 32 + [255-i, i] * 32) * 32 print("add_frame") g.add_frame(data) # expected to raise an error, buffer is not big enough with gifio.GifWriter("/dev/null", 64, 64, displayio.Colorspace.L8) as g: g.add_frame(bytes([3,3,3])) ``` --- locale/circuitpython.pot | 11 +- ports/unix/displayio_colorspace_only.c | 90 ++++++++ .../unix/variants/coverage/mpconfigvariant.mk | 5 + py/argcheck.c | 2 +- py/circuitpy_defns.mk | 6 + py/circuitpy_mpconfig.mk | 3 + shared-bindings/displayio/ColorConverter.c | 1 - shared-bindings/displayio/ColorConverter.h | 1 + shared-bindings/displayio/Colorspace.c | 77 +++++++ shared-bindings/displayio/__init__.c | 45 ---- shared-bindings/displayio/__init__.h | 3 +- shared-bindings/gifio/GifWriter.c | 157 ++++++++++++++ shared-bindings/gifio/GifWriter.h | 41 ++++ shared-bindings/gifio/__init__.c | 47 +++++ shared-bindings/gifio/__init__.h | 0 shared-module/displayio/ColorConverter.c | 32 ++- shared-module/gifio/GifWriter.c | 198 ++++++++++++++++++ shared-module/gifio/GifWriter.h | 40 ++++ shared-module/gifio/__init__.c | 0 shared-module/gifio/__init__.h | 0 20 files changed, 700 insertions(+), 59 deletions(-) create mode 100644 ports/unix/displayio_colorspace_only.c create mode 100644 shared-bindings/displayio/Colorspace.c create mode 100644 shared-bindings/gifio/GifWriter.c create mode 100644 shared-bindings/gifio/GifWriter.h create mode 100644 shared-bindings/gifio/__init__.c create mode 100644 shared-bindings/gifio/__init__.h create mode 100644 shared-module/gifio/GifWriter.c create mode 100644 shared-module/gifio/GifWriter.h create mode 100644 shared-module/gifio/__init__.c create mode 100644 shared-module/gifio/__init__.h diff --git a/locale/circuitpython.pot b/locale/circuitpython.pot index f1f747e7e6..bb5c2ee6b2 100644 --- a/locale/circuitpython.pot +++ b/locale/circuitpython.pot @@ -104,11 +104,11 @@ msgid "%q length must be >= 1" msgstr "" #: py/argcheck.c -msgid "%q must <= %d" +msgid "%q must be %d-%d" msgstr "" -#: py/argcheck.c -msgid "%q must be %d-%d" +#: py/argcheck.c shared-bindings/gifio/GifWriter.c +msgid "%q must be <= %d" msgstr "" #: py/argcheck.c @@ -3073,6 +3073,7 @@ msgstr "" #: shared-bindings/audiocore/WaveFile.c shared-bindings/audiomp3/MP3Decoder.c #: shared-bindings/displayio/OnDiskBitmap.c shared-bindings/synthio/__init__.c +#: shared-module/gifio/GifWriter.c msgid "file must be a file opened in byte mode" msgstr "" @@ -4330,6 +4331,10 @@ msgstr "" msgid "unsupported Xtensa instruction '%s' with %d arguments" msgstr "" +#: shared-module/gifio/GifWriter.c +msgid "unsupported colorspace for GifWriter" +msgstr "" + #: py/objstr.c #, c-format msgid "unsupported format character '%c' (0x%x) at index %d" diff --git a/ports/unix/displayio_colorspace_only.c b/ports/unix/displayio_colorspace_only.c new file mode 100644 index 0000000000..40325ec76a --- /dev/null +++ b/ports/unix/displayio_colorspace_only.c @@ -0,0 +1,90 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2021 Jeff Epler for Adafruit Industries + * + * 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. + */ + +#include "py/enum.h" +#include "py/obj.h" +#include "py/runtime.h" + +#include "shared-bindings/displayio/__init__.h" + +MAKE_ENUM_VALUE(displayio_colorspace_type, displayio_colorspace, RGB888, DISPLAYIO_COLORSPACE_RGB888); +MAKE_ENUM_VALUE(displayio_colorspace_type, displayio_colorspace, RGB565, DISPLAYIO_COLORSPACE_RGB565); +MAKE_ENUM_VALUE(displayio_colorspace_type, displayio_colorspace, RGB565_SWAPPED, DISPLAYIO_COLORSPACE_RGB565_SWAPPED); +MAKE_ENUM_VALUE(displayio_colorspace_type, displayio_colorspace, RGB555, DISPLAYIO_COLORSPACE_RGB555); +MAKE_ENUM_VALUE(displayio_colorspace_type, displayio_colorspace, RGB555_SWAPPED, DISPLAYIO_COLORSPACE_RGB555_SWAPPED); +MAKE_ENUM_VALUE(displayio_colorspace_type, displayio_colorspace, BGR565, DISPLAYIO_COLORSPACE_BGR565); +MAKE_ENUM_VALUE(displayio_colorspace_type, displayio_colorspace, BGR565_SWAPPED, DISPLAYIO_COLORSPACE_BGR565_SWAPPED); +MAKE_ENUM_VALUE(displayio_colorspace_type, displayio_colorspace, BGR555, DISPLAYIO_COLORSPACE_BGR555); +MAKE_ENUM_VALUE(displayio_colorspace_type, displayio_colorspace, BGR555_SWAPPED, DISPLAYIO_COLORSPACE_BGR555_SWAPPED); +MAKE_ENUM_VALUE(displayio_colorspace_type, displayio_colorspace, L8, DISPLAYIO_COLORSPACE_L8); + +//| class Colorspace: +//| """The colorspace for a `ColorConverter` to operate in""" +//| +//| RGB888: Colorspace +//| """The standard 24-bit colorspace. Bits 0-7 are blue, 8-15 are green, and 16-24 are red. (0xRRGGBB)""" +//| +//| RGB565: Colorspace +//| """The standard 16-bit colorspace. Bits 0-4 are blue, bits 5-10 are green, and 11-15 are red (0bRRRRRGGGGGGBBBBB)""" +//| +//| RGB565_SWAPPED: Colorspace +//| """The swapped 16-bit colorspace. First, the high and low 8 bits of the number are swapped, then they are interpreted as for RGB565""" +//| +//| RGB555: Colorspace +//| """The standard 15-bit colorspace. Bits 0-4 are blue, bits 5-9 are green, and 11-14 are red. The top bit is ignored. (0bxRRRRRGGGGGBBBBB)""" +//| +//| RGB555_SWAPPED: Colorspace +//| """The swapped 15-bit colorspace. First, the high and low 8 bits of the number are swapped, then they are interpreted as for RGB555""" +//| +MAKE_ENUM_MAP(displayio_colorspace) { + MAKE_ENUM_MAP_ENTRY(displayio_colorspace, RGB888), + MAKE_ENUM_MAP_ENTRY(displayio_colorspace, RGB565), + MAKE_ENUM_MAP_ENTRY(displayio_colorspace, RGB565_SWAPPED), + MAKE_ENUM_MAP_ENTRY(displayio_colorspace, RGB555), + MAKE_ENUM_MAP_ENTRY(displayio_colorspace, RGB555_SWAPPED), + MAKE_ENUM_MAP_ENTRY(displayio_colorspace, BGR565), + MAKE_ENUM_MAP_ENTRY(displayio_colorspace, BGR565_SWAPPED), + MAKE_ENUM_MAP_ENTRY(displayio_colorspace, BGR555), + MAKE_ENUM_MAP_ENTRY(displayio_colorspace, BGR555_SWAPPED), + MAKE_ENUM_MAP_ENTRY(displayio_colorspace, L8), +}; +STATIC MP_DEFINE_CONST_DICT(displayio_colorspace_locals_dict, displayio_colorspace_locals_table); + +MAKE_PRINTER(displayio, displayio_colorspace); +MAKE_ENUM_TYPE(displayio, ColorSpace, displayio_colorspace); + +STATIC const mp_rom_map_elem_t displayio_module_globals_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_displayio) }, + { MP_ROM_QSTR(MP_QSTR_Colorspace), MP_ROM_PTR(&displayio_colorspace_type) }, +}; +STATIC MP_DEFINE_CONST_DICT(displayio_module_globals, displayio_module_globals_table); + +const mp_obj_module_t displayio_module = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t *)&displayio_module_globals, +}; + +MP_REGISTER_MODULE(MP_QSTR_displayio, displayio_module, CIRCUITPY_DISPLAYIO_COLORSPACE_ONLY); diff --git a/ports/unix/variants/coverage/mpconfigvariant.mk b/ports/unix/variants/coverage/mpconfigvariant.mk index e5d0f7e2d3..e614cd411e 100644 --- a/ports/unix/variants/coverage/mpconfigvariant.mk +++ b/ports/unix/variants/coverage/mpconfigvariant.mk @@ -27,5 +27,10 @@ SRC_C += $(SRC_QRIO) CFLAGS += -DCIRCUITPY_QRIO=1 $(BUILD)/lib/quirc/lib/%.o: CFLAGS += -Wno-shadow -Wno-sign-compare -include shared-module/qrio/quirc_alloc.h +SRC_GIFIO := $(patsubst ../../%,%,$(wildcard ../../shared-bindings/gifio/*.c ../../shared-module/gifio/*.c)) shared/runtime/context_manager_helpers.c displayio_colorspace_only.c shared-module/displayio/ColorConverter.c shared-bindings/util.c +SRC_C += $(SRC_GIFIO) + +CFLAGS += -DCIRCUITPY_GIFIO=1 -DCIRCUITPY_DISPLAYIO_COLORSPACE_ONLY=1 + SRC_C += coverage.c SRC_CXX += coveragecpp.cpp diff --git a/py/argcheck.c b/py/argcheck.c index aa88eeed6c..c2066a7c39 100644 --- a/py/argcheck.c +++ b/py/argcheck.c @@ -165,7 +165,7 @@ mp_int_t mp_arg_validate_int_min(mp_int_t i, mp_int_t min, qstr arg_name) { mp_int_t mp_arg_validate_int_max(mp_int_t i, mp_int_t max, qstr arg_name) { if (i > max) { - mp_raise_ValueError_varg(translate("%q must <= %d"), arg_name, max); + mp_raise_ValueError_varg(translate("%q must be <= %d"), arg_name, max); } return i; } diff --git a/py/circuitpy_defns.mk b/py/circuitpy_defns.mk index 7d2d128a73..ff2933caa4 100644 --- a/py/circuitpy_defns.mk +++ b/py/circuitpy_defns.mk @@ -197,6 +197,9 @@ endif ifeq ($(CIRCUITPY_GETPASS),1) SRC_PATTERNS += getpass/% endif +ifeq ($(CIRCUITPY_GIFIO),1) +SRC_PATTERNS += gifio/% +endif ifeq ($(CIRCUITPY_GNSS),1) SRC_PATTERNS += gnss/% endif @@ -465,6 +468,7 @@ $(filter $(SRC_PATTERNS), \ digitalio/Direction.c \ digitalio/DriveMode.c \ digitalio/Pull.c \ + displayio/Colorspace.c \ fontio/Glyph.c \ math/__init__.c \ microcontroller/ResetReason.c \ @@ -535,6 +539,8 @@ SRC_SHARED_MODULE_ALL = \ gamepadshift/GamePadShift.c \ gamepadshift/__init__.c \ getpass/__init__.c \ + gifio/__init__.c \ + gifio/GifWriter.c \ ipaddress/IPv4Address.c \ ipaddress/__init__.c \ keypad/__init__.c \ diff --git a/py/circuitpy_mpconfig.mk b/py/circuitpy_mpconfig.mk index be373b78a2..443bd41d8b 100644 --- a/py/circuitpy_mpconfig.mk +++ b/py/circuitpy_mpconfig.mk @@ -214,6 +214,9 @@ CFLAGS += -DCIRCUITPY_GAMEPADSHIFT=$(CIRCUITPY_GAMEPADSHIFT) CIRCUITPY_GETPASS ?= $(CIRCUITPY_FULL_BUILD) CFLAGS += -DCIRCUITPY_GETPASS=$(CIRCUITPY_GETPASS) +CIRCUITPY_GIFIO ?= $(CIRCUITPY_FULL_BUILD) +CFLAGS += -DCIRCUITPY_GIFIO=$(CIRCUITPY_GIFIO) + CIRCUITPY_GNSS ?= 0 CFLAGS += -DCIRCUITPY_GNSS=$(CIRCUITPY_GNSS) diff --git a/shared-bindings/displayio/ColorConverter.c b/shared-bindings/displayio/ColorConverter.c index c093c70128..2f40d8bd36 100644 --- a/shared-bindings/displayio/ColorConverter.c +++ b/shared-bindings/displayio/ColorConverter.c @@ -33,7 +33,6 @@ #include "py/enum.h" #include "py/objproperty.h" #include "py/runtime.h" -#include "shared-bindings/microcontroller/Pin.h" #include "shared-bindings/util.h" #include "supervisor/shared/translate.h" diff --git a/shared-bindings/displayio/ColorConverter.h b/shared-bindings/displayio/ColorConverter.h index 018db92c96..12fa8da20f 100644 --- a/shared-bindings/displayio/ColorConverter.h +++ b/shared-bindings/displayio/ColorConverter.h @@ -36,6 +36,7 @@ extern const mp_obj_type_t displayio_colorconverter_type; void common_hal_displayio_colorconverter_construct(displayio_colorconverter_t *self, bool dither, displayio_colorspace_t input_colorspace); void common_hal_displayio_colorconverter_convert(displayio_colorconverter_t *colorconverter, const _displayio_colorspace_t *colorspace, uint32_t input_color, uint32_t *output_color); +uint32_t displayio_colorconverter_convert_pixel(displayio_colorspace_t colorspace, uint32_t pixel); void common_hal_displayio_colorconverter_set_dither(displayio_colorconverter_t *self, bool dither); bool common_hal_displayio_colorconverter_get_dither(displayio_colorconverter_t *self); diff --git a/shared-bindings/displayio/Colorspace.c b/shared-bindings/displayio/Colorspace.c new file mode 100644 index 0000000000..3692dc29bc --- /dev/null +++ b/shared-bindings/displayio/Colorspace.c @@ -0,0 +1,77 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2021 Jeff Epler for Adafruit Industries + * + * 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. + */ + +#include "py/enum.h" +#include "py/obj.h" +#include "py/runtime.h" + +#include "shared-bindings/displayio/__init__.h" + +MAKE_ENUM_VALUE(displayio_colorspace_type, displayio_colorspace, RGB888, DISPLAYIO_COLORSPACE_RGB888); +MAKE_ENUM_VALUE(displayio_colorspace_type, displayio_colorspace, RGB565, DISPLAYIO_COLORSPACE_RGB565); +MAKE_ENUM_VALUE(displayio_colorspace_type, displayio_colorspace, RGB565_SWAPPED, DISPLAYIO_COLORSPACE_RGB565_SWAPPED); +MAKE_ENUM_VALUE(displayio_colorspace_type, displayio_colorspace, RGB555, DISPLAYIO_COLORSPACE_RGB555); +MAKE_ENUM_VALUE(displayio_colorspace_type, displayio_colorspace, RGB555_SWAPPED, DISPLAYIO_COLORSPACE_RGB555_SWAPPED); +MAKE_ENUM_VALUE(displayio_colorspace_type, displayio_colorspace, BGR565, DISPLAYIO_COLORSPACE_BGR565); +MAKE_ENUM_VALUE(displayio_colorspace_type, displayio_colorspace, BGR565_SWAPPED, DISPLAYIO_COLORSPACE_BGR565_SWAPPED); +MAKE_ENUM_VALUE(displayio_colorspace_type, displayio_colorspace, BGR555, DISPLAYIO_COLORSPACE_BGR555); +MAKE_ENUM_VALUE(displayio_colorspace_type, displayio_colorspace, BGR555_SWAPPED, DISPLAYIO_COLORSPACE_BGR555_SWAPPED); +MAKE_ENUM_VALUE(displayio_colorspace_type, displayio_colorspace, L8, DISPLAYIO_COLORSPACE_L8); + +//| class Colorspace: +//| """The colorspace for a `ColorConverter` to operate in""" +//| +//| RGB888: Colorspace +//| """The standard 24-bit colorspace. Bits 0-7 are blue, 8-15 are green, and 16-24 are red. (0xRRGGBB)""" +//| +//| RGB565: Colorspace +//| """The standard 16-bit colorspace. Bits 0-4 are blue, bits 5-10 are green, and 11-15 are red (0bRRRRRGGGGGGBBBBB)""" +//| +//| RGB565_SWAPPED: Colorspace +//| """The swapped 16-bit colorspace. First, the high and low 8 bits of the number are swapped, then they are interpreted as for RGB565""" +//| +//| RGB555: Colorspace +//| """The standard 15-bit colorspace. Bits 0-4 are blue, bits 5-9 are green, and 11-14 are red. The top bit is ignored. (0bxRRRRRGGGGGBBBBB)""" +//| +//| RGB555_SWAPPED: Colorspace +//| """The swapped 15-bit colorspace. First, the high and low 8 bits of the number are swapped, then they are interpreted as for RGB555""" +//| +MAKE_ENUM_MAP(displayio_colorspace) { + MAKE_ENUM_MAP_ENTRY(displayio_colorspace, RGB888), + MAKE_ENUM_MAP_ENTRY(displayio_colorspace, RGB565), + MAKE_ENUM_MAP_ENTRY(displayio_colorspace, RGB565_SWAPPED), + MAKE_ENUM_MAP_ENTRY(displayio_colorspace, RGB555), + MAKE_ENUM_MAP_ENTRY(displayio_colorspace, RGB555_SWAPPED), + MAKE_ENUM_MAP_ENTRY(displayio_colorspace, BGR565), + MAKE_ENUM_MAP_ENTRY(displayio_colorspace, BGR565_SWAPPED), + MAKE_ENUM_MAP_ENTRY(displayio_colorspace, BGR555), + MAKE_ENUM_MAP_ENTRY(displayio_colorspace, BGR555_SWAPPED), + MAKE_ENUM_MAP_ENTRY(displayio_colorspace, L8), +}; +STATIC MP_DEFINE_CONST_DICT(displayio_colorspace_locals_dict, displayio_colorspace_locals_table); + +MAKE_PRINTER(displayio, displayio_colorspace); +MAKE_ENUM_TYPE(displayio, ColorSpace, displayio_colorspace); diff --git a/shared-bindings/displayio/__init__.c b/shared-bindings/displayio/__init__.c index 7ac2a84903..5588dc83e9 100644 --- a/shared-bindings/displayio/__init__.c +++ b/shared-bindings/displayio/__init__.c @@ -69,50 +69,6 @@ STATIC mp_obj_t displayio_release_displays(void) { } MP_DEFINE_CONST_FUN_OBJ_0(displayio_release_displays_obj, displayio_release_displays); -MAKE_ENUM_VALUE(displayio_colorspace_type, displayio_colorspace, RGB888, DISPLAYIO_COLORSPACE_RGB888); -MAKE_ENUM_VALUE(displayio_colorspace_type, displayio_colorspace, RGB565, DISPLAYIO_COLORSPACE_RGB565); -MAKE_ENUM_VALUE(displayio_colorspace_type, displayio_colorspace, RGB565_SWAPPED, DISPLAYIO_COLORSPACE_RGB565_SWAPPED); -MAKE_ENUM_VALUE(displayio_colorspace_type, displayio_colorspace, RGB555, DISPLAYIO_COLORSPACE_RGB555); -MAKE_ENUM_VALUE(displayio_colorspace_type, displayio_colorspace, RGB555_SWAPPED, DISPLAYIO_COLORSPACE_RGB555_SWAPPED); -MAKE_ENUM_VALUE(displayio_colorspace_type, displayio_colorspace, BGR565, DISPLAYIO_COLORSPACE_BGR565); -MAKE_ENUM_VALUE(displayio_colorspace_type, displayio_colorspace, BGR565_SWAPPED, DISPLAYIO_COLORSPACE_BGR565_SWAPPED); -MAKE_ENUM_VALUE(displayio_colorspace_type, displayio_colorspace, BGR555, DISPLAYIO_COLORSPACE_BGR555); -MAKE_ENUM_VALUE(displayio_colorspace_type, displayio_colorspace, BGR555_SWAPPED, DISPLAYIO_COLORSPACE_BGR555_SWAPPED); - -//| class Colorspace: -//| """The colorspace for a `ColorConverter` to operate in""" -//| -//| RGB888: Colorspace -//| """The standard 24-bit colorspace. Bits 0-7 are blue, 8-15 are green, and 16-24 are red. (0xRRGGBB)""" -//| -//| RGB565: Colorspace -//| """The standard 16-bit colorspace. Bits 0-4 are blue, bits 5-10 are green, and 11-15 are red (0bRRRRRGGGGGGBBBBB)""" -//| -//| RGB565_SWAPPED: Colorspace -//| """The swapped 16-bit colorspace. First, the high and low 8 bits of the number are swapped, then they are interpreted as for RGB565""" -//| -//| RGB555: Colorspace -//| """The standard 15-bit colorspace. Bits 0-4 are blue, bits 5-9 are green, and 11-14 are red. The top bit is ignored. (0bxRRRRRGGGGGBBBBB)""" -//| -//| RGB555_SWAPPED: Colorspace -//| """The swapped 15-bit colorspace. First, the high and low 8 bits of the number are swapped, then they are interpreted as for RGB555""" -//| -MAKE_ENUM_MAP(displayio_colorspace) { - MAKE_ENUM_MAP_ENTRY(displayio_colorspace, RGB888), - MAKE_ENUM_MAP_ENTRY(displayio_colorspace, RGB565), - MAKE_ENUM_MAP_ENTRY(displayio_colorspace, RGB565_SWAPPED), - MAKE_ENUM_MAP_ENTRY(displayio_colorspace, RGB555), - MAKE_ENUM_MAP_ENTRY(displayio_colorspace, RGB555_SWAPPED), - MAKE_ENUM_MAP_ENTRY(displayio_colorspace, BGR565), - MAKE_ENUM_MAP_ENTRY(displayio_colorspace, BGR565_SWAPPED), - MAKE_ENUM_MAP_ENTRY(displayio_colorspace, BGR555), - MAKE_ENUM_MAP_ENTRY(displayio_colorspace, BGR555_SWAPPED), -}; -STATIC MP_DEFINE_CONST_DICT(displayio_colorspace_locals_dict, displayio_colorspace_locals_table); - -MAKE_PRINTER(displayio, displayio_colorspace); -MAKE_ENUM_TYPE(displayio, ColorSpace, displayio_colorspace); - STATIC const mp_rom_map_elem_t displayio_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_displayio) }, @@ -135,7 +91,6 @@ STATIC const mp_rom_map_elem_t displayio_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_release_displays), MP_ROM_PTR(&displayio_release_displays_obj) }, }; - STATIC MP_DEFINE_CONST_DICT(displayio_module_globals, displayio_module_globals_table); const mp_obj_module_t displayio_module = { diff --git a/shared-bindings/displayio/__init__.h b/shared-bindings/displayio/__init__.h index 938fcf3d98..b297e4d755 100644 --- a/shared-bindings/displayio/__init__.h +++ b/shared-bindings/displayio/__init__.h @@ -40,7 +40,7 @@ typedef enum { CHIP_SELECT_TOGGLE_EVERY_BYTE } display_chip_select_behavior_t; -typedef enum { +typedef enum displayio_colorspace { DISPLAYIO_COLORSPACE_RGB888, DISPLAYIO_COLORSPACE_RGB565, DISPLAYIO_COLORSPACE_RGB555, @@ -50,6 +50,7 @@ typedef enum { DISPLAYIO_COLORSPACE_BGR555, DISPLAYIO_COLORSPACE_BGR565_SWAPPED, DISPLAYIO_COLORSPACE_BGR555_SWAPPED, + DISPLAYIO_COLORSPACE_L8, } displayio_colorspace_t; typedef bool (*display_bus_bus_reset)(mp_obj_t bus); diff --git a/shared-bindings/gifio/GifWriter.c b/shared-bindings/gifio/GifWriter.c new file mode 100644 index 0000000000..8c9a9a3f3b --- /dev/null +++ b/shared-bindings/gifio/GifWriter.c @@ -0,0 +1,157 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2021 Jeff Epler for Adafruit Industries + * + * 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. + */ + +#include "py/obj.h" +#if MICROPY_VFS +#include "extmod/vfs.h" +#endif +#include "py/runtime.h" +#include "shared-bindings/gifio/GifWriter.h" +#include "shared-module/gifio/GifWriter.h" +#include "shared/runtime/context_manager_helpers.h" + +//| class GifWriter: +//| def __init__(self, file: Union[Typing.IO.BinaryIO, str], width:int, height:int, colorspace: displayio.Colorspace, loop:bool=True) -> None: +//| """Construct a GifWriter object +//| +//| :param file: Either a file open in bytes mode, or the name of a file to open in bytes mode. +//| :param width: The width of the image. All frames must have the same width. +//| :param height: The height of the image. All frames must have the same height. +//| :param colorspace: The colorspace of the image. All frames must have the same colorspace. Only 1- and 2-byte colorspace are supported, not ``RGB888``. +//| """ +//| ... +//| +static mp_obj_t gifio_gifwriter_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { + enum { ARG_file, ARG_width, ARG_height, ARG_colorspace, ARG_loop }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_file, MP_ARG_OBJ | MP_ARG_REQUIRED, {.u_obj = NULL} }, + { MP_QSTR_width, MP_ARG_INT | MP_ARG_REQUIRED, {.u_int = 0} }, + { MP_QSTR_height, MP_ARG_INT | MP_ARG_REQUIRED, {.u_int = 0} }, + { MP_QSTR_colorspace, MP_ARG_OBJ | MP_ARG_REQUIRED, {.u_obj = NULL} }, + { MP_QSTR_loop, MP_ARG_BOOL, { .u_bool = true } }, + }; + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + mp_obj_t file = args[ARG_file].u_obj; + bool own_file = false; + if (mp_obj_is_str(file)) { + file = mp_call_function_2(MP_OBJ_FROM_PTR(&mp_builtin_open_obj), file, MP_OBJ_NEW_QSTR(MP_QSTR_wb)); + own_file = true; + } + + gifio_gifwriter_t *self = m_new_obj(gifio_gifwriter_t); + self->base.type = &gifio_gifwriter_type; + shared_module_gifio_gifwriter_construct( + self, + file, + args[ARG_width].u_int, + args[ARG_height].u_int, + (displayio_colorspace_t)cp_enum_value(&displayio_colorspace_type, args[ARG_colorspace].u_obj), + args[ARG_loop].u_bool, + own_file); + + return self; +} + + +//| def __enter__(self) -> GifWriter: +//| """No-op used by Context Managers.""" +//| ... +// Provided by context manager helper. + +//| def __exit__(self) -> None: +//| """Automatically deinitializes the hardware when exiting a context. See +//| :ref:`lifetime-and-contextmanagers` for more info.""" +//| ... +//| +static mp_obj_t gifio_gifwriter___exit__(size_t n_args, const mp_obj_t *args) { + gifio_gifwriter_t *self = MP_OBJ_TO_PTR(args[0]); + shared_module_gifio_gifwriter_deinit(self); + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(gifio_gifwriter___exit___obj, 4, 4, gifio_gifwriter___exit__); + +//| def deinit(self) -> None: +//| """Close the underlying file.""" +//| ... +//| +static mp_obj_t gifio_gifwriter_deinit(mp_obj_t self_in) { + gifio_gifwriter_t *self = MP_OBJ_TO_PTR(self_in); + shared_module_gifio_gifwriter_deinit(self); + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_1(gifio_gifwriter_deinit_obj, gifio_gifwriter_deinit); + +//| def add_frame(self, bitmap: ReadableBuffer, delay: float = 0.1) -> None: +//| """Add a frame to the GIF. +//| +//| :param bitmap: The frame data +//| :param delay: The frame delay in seconds. The GIF format rounds this to the nearest 1/100 second, and the largest permitted value is 655 seconds. +//| """ +//| ... +static mp_obj_t gifio_gifwriter_add_frame(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_bitmap, ARG_delay }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_bitmap, MP_ARG_OBJ | MP_ARG_REQUIRED, {.u_obj = NULL} }, + { MP_QSTR_delay, MP_ARG_OBJ, {.u_obj = NULL} }, + }; + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + gifio_gifwriter_t *self = MP_OBJ_TO_PTR(pos_args[0]); + shared_module_gifio_gifwriter_check_for_deinit(self); + + + mp_float_t delay = mp_arg_validate_obj_float_non_negative(args[ARG_delay].u_obj, MICROPY_FLOAT_CONST(0.1), MP_QSTR_delay); + if (delay > MICROPY_FLOAT_CONST(655.)) { + mp_raise_ValueError_varg(translate("%q must be <= %d"), MP_QSTR_delay, 655); + } + + int delay_centiseconds = (int)(delay * 100); + mp_buffer_info_t bufinfo; + mp_get_buffer_raise(args[ARG_bitmap].u_obj, &bufinfo, MP_BUFFER_READ); + shared_module_gifio_gifwriter_add_frame(self, &bufinfo, delay_centiseconds); + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_KW(gifio_gifwriter_add_frame_obj, 1, gifio_gifwriter_add_frame); + +STATIC const mp_rom_map_elem_t gifio_gifwriter_locals_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_GifWriter) }, + { MP_ROM_QSTR(MP_QSTR___enter__), MP_ROM_PTR(&default___enter___obj) }, + { MP_ROM_QSTR(MP_QSTR___exit__), MP_ROM_PTR(&gifio_gifwriter___exit___obj) }, + { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&gifio_gifwriter_deinit_obj) }, + { MP_ROM_QSTR(MP_QSTR_add_frame), MP_ROM_PTR(&gifio_gifwriter_add_frame_obj) }, +}; + +STATIC MP_DEFINE_CONST_DICT(gifio_gifwriter_locals, gifio_gifwriter_locals_table); + +const mp_obj_type_t gifio_gifwriter_type = { + { &mp_type_type }, + .name = MP_QSTR_GifWriter, + .make_new = gifio_gifwriter_make_new, + .locals_dict = (mp_obj_dict_t *)&gifio_gifwriter_locals, +}; diff --git a/shared-bindings/gifio/GifWriter.h b/shared-bindings/gifio/GifWriter.h new file mode 100644 index 0000000000..203169cd75 --- /dev/null +++ b/shared-bindings/gifio/GifWriter.h @@ -0,0 +1,41 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2021 Jeff Epler for Adafruit Industries + * + * 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. + */ + +#include "py/obj.h" + +#pragma once + +typedef struct gifio_gifwriter gifio_gifwriter_t; +typedef enum displayio_colorspace displayio_colorspace_t; + +extern const mp_obj_type_t gifio_gifwriter_type; + +void shared_module_gifio_gifwriter_construct(gifio_gifwriter_t *self, mp_obj_t *file, int width, int height, displayio_colorspace_t colorspace, bool loop, bool own_file); +void shared_module_gifio_gifwriter_check_for_deinit(gifio_gifwriter_t *self); +bool shared_module_gifio_gifwriter_deinited(gifio_gifwriter_t *self); +void shared_module_gifio_gifwriter_deinit(gifio_gifwriter_t *self); +void shared_module_gifio_gifwriter_add_frame(gifio_gifwriter_t *self, const mp_buffer_info_t *buf, int16_t delay); +void shared_module_gifio_gifwriter_close(gifio_gifwriter_t *self); diff --git a/shared-bindings/gifio/__init__.c b/shared-bindings/gifio/__init__.c new file mode 100644 index 0000000000..317eebce9b --- /dev/null +++ b/shared-bindings/gifio/__init__.c @@ -0,0 +1,47 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2021 Jeff Epler for Adafruit Industries + * + * 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. + */ +#include "py/obj.h" +#include "py/runtime.h" +#include "py/mphal.h" +#include "shared-bindings/gifio/GifWriter.h" +#include "shared-bindings/util.h" + +//| """Access GIF-format images +//| """ +//| +STATIC const mp_rom_map_elem_t gifio_module_globals_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_gifio) }, + { MP_OBJ_NEW_QSTR(MP_QSTR_GifWriter), MP_ROM_PTR(&gifio_gifwriter_type)}, +}; + +STATIC MP_DEFINE_CONST_DICT(gifio_module_globals, gifio_module_globals_table); + +const mp_obj_module_t gifio_module = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t *)&gifio_module_globals, +}; + +MP_REGISTER_MODULE(MP_QSTR_gifio, gifio_module, CIRCUITPY_GIFIO); diff --git a/shared-bindings/gifio/__init__.h b/shared-bindings/gifio/__init__.h new file mode 100644 index 0000000000..e69de29bb2 diff --git a/shared-module/displayio/ColorConverter.c b/shared-module/displayio/ColorConverter.c index be5b7d31a7..228d63d03b 100644 --- a/shared-module/displayio/ColorConverter.c +++ b/shared-module/displayio/ColorConverter.c @@ -145,15 +145,10 @@ void common_hal_displayio_colorconverter_make_opaque(displayio_colorconverter_t self->transparent_color = NO_TRANSPARENT_COLOR; } -void displayio_colorconverter_convert(displayio_colorconverter_t *self, const _displayio_colorspace_t *colorspace, const displayio_input_pixel_t *input_pixel, displayio_output_pixel_t *output_color) { - uint32_t pixel = input_pixel->pixel; - if (self->transparent_color == pixel) { - output_color->opaque = false; - return; - } - - switch (self->input_colorspace) { +// Convert a single input pixel to RGB888 +uint32_t displayio_colorconverter_convert_pixel(displayio_colorspace_t colorspace, uint32_t pixel) { + switch (colorspace) { case DISPLAYIO_COLORSPACE_RGB565_SWAPPED: pixel = __builtin_bswap16(pixel); MP_FALLTHROUGH; @@ -198,10 +193,31 @@ void displayio_colorconverter_convert(displayio_colorconverter_t *self, const _d } break; + default: case DISPLAYIO_COLORSPACE_RGB888: break; + + case DISPLAYIO_COLORSPACE_L8: { + uint32_t l8 = pixel & 0xff; + pixel = l8 * 0x010101; + } + break; } + return pixel; +} + +void displayio_colorconverter_convert(displayio_colorconverter_t *self, const _displayio_colorspace_t *colorspace, const displayio_input_pixel_t *input_pixel, displayio_output_pixel_t *output_color) { + uint32_t pixel = input_pixel->pixel; + + if (self->transparent_color == pixel) { + output_color->opaque = false; + return; + } + + pixel = displayio_colorconverter_convert_pixel(self->input_colorspace, pixel); + + if (self->dither) { uint8_t randr = (displayio_colorconverter_dither_noise_2(input_pixel->tile_x,input_pixel->tile_y)); uint8_t randg = (displayio_colorconverter_dither_noise_2(input_pixel->tile_x + 33,input_pixel->tile_y)); diff --git a/shared-module/gifio/GifWriter.c b/shared-module/gifio/GifWriter.c new file mode 100644 index 0000000000..a428b17a97 --- /dev/null +++ b/shared-module/gifio/GifWriter.c @@ -0,0 +1,198 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2021 Jeff Epler for Adafruit Industries + * Copyright (c) 2013-2021 Ibrahim Abdelkader + * Copyright (c) 2013-2021 Kwabena W. Agyeman + * + * 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. + */ + +#include "py/runtime.h" + +#include "shared-module/gifio/GifWriter.h" +#include "shared-bindings/gifio/GifWriter.h" +#include "shared-bindings/displayio/ColorConverter.h" +#include "shared-bindings/util.h" + +#define BLOCK_SIZE (126) // (2^7) - 2 // (DO NOT CHANGE!) + +static void handle_error(const char *what, int error) { + if (error != 0) { + mp_raise_OSError(error); + } +} + +static void write_data(gifio_gifwriter_t *self, const void *data, size_t size) { + int error = 0; + self->file_proto->write(self->file, data, size, &error); + handle_error("write_data", error); +} + +static void write_byte(gifio_gifwriter_t *self, uint8_t value) { + write_data(self, &value, sizeof(value)); +} + +static void write_long(gifio_gifwriter_t *self, uint32_t value) { + write_data(self, &value, sizeof(value)); +} + +static void write_word(gifio_gifwriter_t *self, uint16_t value) { + write_data(self, &value, sizeof(value)); +} + +void shared_module_gifio_gifwriter_construct(gifio_gifwriter_t *self, mp_obj_t *file, int width, int height, displayio_colorspace_t colorspace, bool loop, bool own_file) { + self->file = file; + self->file_proto = mp_proto_get_or_throw(MP_QSTR_protocol_stream, file); + if (self->file_proto->is_text) { + mp_raise_TypeError(translate("file must be a file opened in byte mode")); + } + self->width = width; + self->height = height; + self->colorspace = colorspace; + self->own_file = own_file; + + write_data(self, "GIF89a", 6); + write_word(self, width); + write_word(self, height); + write_data(self, (uint8_t []) {0xF6, 0x00, 0x00}, 3); + + if (colorspace == DISPLAYIO_COLORSPACE_RGB888) { + mp_raise_TypeError(translate("unsupported colorspace for GifWriter")); + } + + bool color = (colorspace != DISPLAYIO_COLORSPACE_L8); + + if (color) { + for (int i = 0; i < 128; i++) { + int red = (int)(((((i & 0x60) >> 5) * 255) + 1.5) / 3); + int green = (int)(((((i & 0x1C) >> 2) * 255) + 3.5) / 7); + int blue = (int)((((i & 0x3) * 255) + 1.5) / 3); + write_data(self, (uint8_t []) {red, green, blue}, 3); + } + } else { + for (int i = 0; i < 128; i++) { + int gray = (int)(((i * 255) + 63.5) / 127); + write_data(self, (uint8_t []) {gray, gray, gray}, 3); + } + } + + if (loop) { + write_data(self, (uint8_t []) {'!', 0xFF, 0x0B}, 3); + write_data(self, "NETSCAPE2.0", 11); + write_data(self, (uint8_t []) {0x03, 0x01, 0x00, 0x00, 0x00}, 5); + } + + +} + +bool shared_module_gifio_gifwriter_deinited(gifio_gifwriter_t *self) { + return !self->file; +} + +void shared_module_gifio_gifwriter_check_for_deinit(gifio_gifwriter_t *self) { + if (shared_module_gifio_gifwriter_deinited(self)) { + raise_deinited_error(); + } +} + +void shared_module_gifio_gifwriter_deinit(gifio_gifwriter_t *self) { + if (!shared_module_gifio_gifwriter_deinited(self)) { + shared_module_gifio_gifwriter_close(self); + } +} + +void shared_module_gifio_gifwriter_add_frame(gifio_gifwriter_t *self, const mp_buffer_info_t *bufinfo, int16_t delay) { + if (delay) { + write_data(self, (uint8_t []) {'!', 0xF9, 0x04, 0x04}, 4); + write_word(self, delay); + write_word(self, 0); // end + } + + write_byte(self, 0x2C); + write_long(self, 0); + write_word(self, self->width); + write_word(self, self->height); + write_data(self, (uint8_t []) {0x00, 0x07}, 2); // 7-bits + + int pixel_count = self->width * self->height; + int blocks = (pixel_count + BLOCK_SIZE - 1) / BLOCK_SIZE; + + uint8_t block_data[2 + BLOCK_SIZE]; + block_data[1] = 0x80; + + if (self->colorspace == DISPLAYIO_COLORSPACE_L8) { + mp_get_index(&mp_type_memoryview, bufinfo->len, MP_OBJ_NEW_SMALL_INT(pixel_count - 1), false); + + uint8_t *pixels = bufinfo->buf; + for (int i = 0; i < blocks; i++) { + assert(pixel_count >= 0); + int block_size = MIN(BLOCK_SIZE, pixel_count); + pixel_count -= block_size; + block_data[0] = 1 + block_size; + for (int j = 0; j < block_size; j++) { + block_data[j + 2] = (*pixels++) >> 1; + } + write_data(self, block_data, 2 + block_size); + } + } else { + mp_get_index(&mp_type_memoryview, bufinfo->len, MP_OBJ_NEW_SMALL_INT(2 * pixel_count - 1), false); + + uint16_t *pixels = bufinfo->buf; + for (int i = 0; i < blocks; i++) { + int block_size = MIN(BLOCK_SIZE, pixel_count); + pixel_count -= block_size; + + block_data[0] = 1 + block_size; + for (int j = 0; j < block_size; j++) { + int pixel = displayio_colorconverter_convert_pixel(self->colorspace, (*pixels++)); + int red = (pixel >> (16 + 6)) & 0x3; + int green = (pixel >> (8 + 5)) & 0x7; + int blue = (pixel >> 6) & 0x3; + block_data[j + 2] = (red << 5) | (green << 2) | blue; + } + write_data(self, block_data, 2 + block_size); + } + } + + write_data(self, (uint8_t []) {0x01, 0x81, 0x00}, 3); // end code + + int error = 0; + self->file_proto->ioctl(self->file, MP_STREAM_FLUSH, 0, &error); + handle_error("flush", error); +} + +void shared_module_gifio_gifwriter_close(gifio_gifwriter_t *self) { + // we want to ensure the stream is closed even if the first write failed, so we don't use write_data + int error1 = 0; + self->file_proto->write(self->file, ";", 1, &error1); + + int error2 = 0; + if (self->own_file) { + self->file_proto->ioctl(self->file, MP_STREAM_CLOSE, 0, &error2); + } else { + self->file_proto->ioctl(self->file, MP_STREAM_FLUSH, 0, &error2); + } + self->file = NULL; + + handle_error("write", error1); + handle_error("close", error2); +} diff --git a/shared-module/gifio/GifWriter.h b/shared-module/gifio/GifWriter.h new file mode 100644 index 0000000000..e16de7c06b --- /dev/null +++ b/shared-module/gifio/GifWriter.h @@ -0,0 +1,40 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2021 Jeff Epler for Adafruit Industries + * + * 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. + */ + +#pragma once + +#include "py/obj.h" +#include "py/stream.h" +#include "shared-bindings/displayio/__init__.h" + +typedef struct gifio_gifwriter { + mp_obj_base_t base; + mp_obj_t *file; + const mp_stream_p_t *file_proto; + displayio_colorspace_t colorspace; + int width, height; + bool own_file; +} gifio_gifwriter_t; diff --git a/shared-module/gifio/__init__.c b/shared-module/gifio/__init__.c new file mode 100644 index 0000000000..e69de29bb2 diff --git a/shared-module/gifio/__init__.h b/shared-module/gifio/__init__.h new file mode 100644 index 0000000000..e69de29bb2 From b0203381db3d3b13e03ae536dbc1c50b9400751a Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Thu, 21 Oct 2021 15:11:48 -0500 Subject: [PATCH 02/10] Update module list in test --- tests/unix/extra_coverage.py.exp | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/tests/unix/extra_coverage.py.exp b/tests/unix/extra_coverage.py.exp index 893a6b72c3..3a1e7f280b 100644 --- a/tests/unix/extra_coverage.py.exp +++ b/tests/unix/extra_coverage.py.exp @@ -31,13 +31,14 @@ mport builtins micropython _thread array binascii btree cexample cmath -collections cppexample errno ffi -framebuf gc hashlib json -math qrio re sys -termios ubinascii uctypes uerrno -uheapq uio ujson ulab -uos urandom ure uselect -ustruct utime utimeq uzlib +collections cppexample displayio errno +ffi framebuf gc gifio +hashlib json math qrio +re sys termios ubinascii +uctypes uerrno uheapq uio +ujson ulab uos urandom +ure uselect ustruct utime +utimeq uzlib ime utime utimeq From 081f636c1771f62af5dc5077db540896b30b378d Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Thu, 21 Oct 2021 16:02:42 -0500 Subject: [PATCH 03/10] Fix typing --- shared-bindings/gifio/GifWriter.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shared-bindings/gifio/GifWriter.c b/shared-bindings/gifio/GifWriter.c index 8c9a9a3f3b..6131550bf4 100644 --- a/shared-bindings/gifio/GifWriter.c +++ b/shared-bindings/gifio/GifWriter.c @@ -34,7 +34,7 @@ #include "shared/runtime/context_manager_helpers.h" //| class GifWriter: -//| def __init__(self, file: Union[Typing.IO.BinaryIO, str], width:int, height:int, colorspace: displayio.Colorspace, loop:bool=True) -> None: +//| def __init__(self, file: Union[typing.BinaryIO, str], width:int, height:int, colorspace: displayio.Colorspace, loop:bool=True) -> None: //| """Construct a GifWriter object //| //| :param file: Either a file open in bytes mode, or the name of a file to open in bytes mode. From 8c7760b1a619e52401a3c2714f1f7aa9c14431e5 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Thu, 21 Oct 2021 16:05:48 -0500 Subject: [PATCH 04/10] don't include gifio on samd21 boards --- ports/atmel-samd/mpconfigport.mk | 1 + 1 file changed, 1 insertion(+) diff --git a/ports/atmel-samd/mpconfigport.mk b/ports/atmel-samd/mpconfigport.mk index 818c9f111a..1652032311 100644 --- a/ports/atmel-samd/mpconfigport.mk +++ b/ports/atmel-samd/mpconfigport.mk @@ -49,6 +49,7 @@ CIRCUITPY_COUNTIO ?= 0 # Not enough RAM for framebuffers CIRCUITPY_FRAMEBUFFERIO ?= 0 CIRCUITPY_FREQUENCYIO ?= 0 +CIRCUITPY_GIFIO ?= 0 CIRCUITPY_I2CPERIPHERAL ?= 0 CIRCUITPY_JSON ?= 0 CIRCUITPY_KEYPAD ?= 0 From 3e020a73a8e17c82f7a51320a55c6310a283c926 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Thu, 21 Oct 2021 16:45:04 -0500 Subject: [PATCH 05/10] Disable gifio if no displayio & for small boards Technically all gifio needs from displayio is the definition of colorspaces, but let's just disable it for now. --- ports/atmel-samd/boards/pybadge/mpconfigboard.mk | 1 + ports/atmel-samd/boards/pygamer/mpconfigboard.mk | 1 + ports/stm/boards/espruino_pico/mpconfigboard.mk | 1 + ports/stm/boards/pyb_nano_v2/mpconfigboard.mk | 1 + py/circuitpy_mpconfig.mk | 4 ++++ 5 files changed, 8 insertions(+) diff --git a/ports/atmel-samd/boards/pybadge/mpconfigboard.mk b/ports/atmel-samd/boards/pybadge/mpconfigboard.mk index b5fda72f90..7f4278c90d 100644 --- a/ports/atmel-samd/boards/pybadge/mpconfigboard.mk +++ b/ports/atmel-samd/boards/pybadge/mpconfigboard.mk @@ -12,6 +12,7 @@ LONGINT_IMPL = MPZ CIRCUITPY_AESIO = 0 CIRCUITPY_GAMEPADSHIFT = 1 +CIRCUITPY_GIFIO = 0 CIRCUITPY_STAGE = 1 FROZEN_MPY_DIRS += $(TOP)/frozen/circuitpython-stage/pybadge diff --git a/ports/atmel-samd/boards/pygamer/mpconfigboard.mk b/ports/atmel-samd/boards/pygamer/mpconfigboard.mk index 5b5a84bb78..408efc9dc0 100644 --- a/ports/atmel-samd/boards/pygamer/mpconfigboard.mk +++ b/ports/atmel-samd/boards/pygamer/mpconfigboard.mk @@ -12,6 +12,7 @@ LONGINT_IMPL = MPZ CIRCUITPY_AESIO = 0 CIRCUITPY_GAMEPADSHIFT = 1 +CIRCUITPY_GIFIO = 0 CIRCUITPY_STAGE = 1 FROZEN_MPY_DIRS += $(TOP)/frozen/circuitpython-stage/pygamer diff --git a/ports/stm/boards/espruino_pico/mpconfigboard.mk b/ports/stm/boards/espruino_pico/mpconfigboard.mk index 851357b4d6..a078b4841a 100644 --- a/ports/stm/boards/espruino_pico/mpconfigboard.mk +++ b/ports/stm/boards/espruino_pico/mpconfigboard.mk @@ -23,6 +23,7 @@ CIRCUITPY_AUDIOPWMIO = 0 CIRCUITPY_BUSDEVICE = 0 CIRCUITPY_BITMAPTOOLS = 0 CIRCUITPY_FRAMEBUFFERIO = 0 +CIRCUITPY_GIFIO = 0 CIRCUITPY_KEYPAD = 0 CIRCUITPY_MIDI = 0 CIRCUITPY_MSGPACK = 0 diff --git a/ports/stm/boards/pyb_nano_v2/mpconfigboard.mk b/ports/stm/boards/pyb_nano_v2/mpconfigboard.mk index 9f6a02c8d0..74ce665a5b 100644 --- a/ports/stm/boards/pyb_nano_v2/mpconfigboard.mk +++ b/ports/stm/boards/pyb_nano_v2/mpconfigboard.mk @@ -17,6 +17,7 @@ LD_FILE = boards/STM32F411_fs.ld CIRCUITPY_AUDIOCORE = 0 CIRCUITPY_AUDIOPWMIO = 0 CIRCUITPY_KEYPAD = 0 +CIRCUITPY_GIFIO = 0 CIRCUITPY_MIDI = 0 CIRCUITPY_MSGPACK = 0 CIRCUITPY_BITMAPTOOLS = 0 diff --git a/py/circuitpy_mpconfig.mk b/py/circuitpy_mpconfig.mk index 443bd41d8b..04f0778290 100644 --- a/py/circuitpy_mpconfig.mk +++ b/py/circuitpy_mpconfig.mk @@ -214,7 +214,11 @@ CFLAGS += -DCIRCUITPY_GAMEPADSHIFT=$(CIRCUITPY_GAMEPADSHIFT) CIRCUITPY_GETPASS ?= $(CIRCUITPY_FULL_BUILD) CFLAGS += -DCIRCUITPY_GETPASS=$(CIRCUITPY_GETPASS) +ifeq ($(CIRCUITPY_DISPLAYIO),1) CIRCUITPY_GIFIO ?= $(CIRCUITPY_FULL_BUILD) +else +CIRCUITPY_GIFIO ?= 0 +endif CFLAGS += -DCIRCUITPY_GIFIO=$(CIRCUITPY_GIFIO) CIRCUITPY_GNSS ?= 0 From 7d6ac96001a03ed090449cdda59c3291d4eef026 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Tue, 26 Oct 2021 11:11:28 -0500 Subject: [PATCH 06/10] GifWriter: improve efficiency * Increase colorspace conversion efficiency. This not only avoids a function call, it avoids the time-consuming switch statement in conver_pixel (replacing it with a single conditional on the byteswap flag + accounting for BGR/RGB during palette creation) * Buffer all the bytes of a single frame together. By reducing low level write calls we get a decent speed increase even though it increases data-shuffling a bit. Together with some other changes that enable "double buffered" camera capture, this gets me to 8.8fps capturing QVGA (320x240) gifs and 11fps capturing 240x240 square gifs. --- shared-module/gifio/GifWriter.c | 95 +++++++++++++++++++++++---------- shared-module/gifio/GifWriter.h | 4 ++ 2 files changed, 70 insertions(+), 29 deletions(-) diff --git a/shared-module/gifio/GifWriter.c b/shared-module/gifio/GifWriter.c index a428b17a97..d5594538fe 100644 --- a/shared-module/gifio/GifWriter.c +++ b/shared-module/gifio/GifWriter.c @@ -26,6 +26,9 @@ * THE SOFTWARE. */ +#include + +#include "py/gc.h" #include "py/runtime.h" #include "shared-module/gifio/GifWriter.h" @@ -35,16 +38,30 @@ #define BLOCK_SIZE (126) // (2^7) - 2 // (DO NOT CHANGE!) -static void handle_error(const char *what, int error) { - if (error != 0) { - mp_raise_OSError(error); +static void handle_error(gifio_gifwriter_t *self) { + if (self->error != 0) { + mp_raise_OSError(self->error); } } -static void write_data(gifio_gifwriter_t *self, const void *data, size_t size) { +static void flush_data(gifio_gifwriter_t *self) { + if (self->cur == 0) { + return; + } int error = 0; - self->file_proto->write(self->file, data, size, &error); - handle_error("write_data", error); + self->file_proto->write(self->file, self->data, self->cur, &error); + self->cur = 0; + if (error != 0) { + self->error = error; + } +} + +// These "write" calls _MUST_ have enough buffer space available! This is +// ensured by allocating the proper buffer size in construct. +static void write_data(gifio_gifwriter_t *self, const void *data, size_t size) { + assert(self->cur + size <= self->size); + memcpy(self->data + self->cur, data, size); + self->cur += size; } static void write_byte(gifio_gifwriter_t *self, uint8_t value) { @@ -70,23 +87,44 @@ void shared_module_gifio_gifwriter_construct(gifio_gifwriter_t *self, mp_obj_t * self->colorspace = colorspace; self->own_file = own_file; + size_t nblocks = (width * height + 125) / 126; + self->size = nblocks * 128 + 4; + self->data = gc_alloc(self->size, 0, false); + self->cur = 0; + self->error = 0; + write_data(self, "GIF89a", 6); write_word(self, width); write_word(self, height); write_data(self, (uint8_t []) {0xF6, 0x00, 0x00}, 3); - if (colorspace == DISPLAYIO_COLORSPACE_RGB888) { - mp_raise_TypeError(translate("unsupported colorspace for GifWriter")); + switch (colorspace) { + case DISPLAYIO_COLORSPACE_RGB565: + case DISPLAYIO_COLORSPACE_RGB565_SWAPPED: + case DISPLAYIO_COLORSPACE_BGR565: + case DISPLAYIO_COLORSPACE_BGR565_SWAPPED: + case DISPLAYIO_COLORSPACE_L8: + break; + + default: + mp_raise_TypeError(translate("unsupported colorspace for GifWriter")); } bool color = (colorspace != DISPLAYIO_COLORSPACE_L8); + bool bgr = (colorspace == DISPLAYIO_COLORSPACE_BGR565 || colorspace == DISPLAYIO_COLORSPACE_BGR565_SWAPPED); + self->byteswap = (colorspace == DISPLAYIO_COLORSPACE_RGB565_SWAPPED || colorspace == DISPLAYIO_COLORSPACE_BGR565_SWAPPED); + if (color) { for (int i = 0; i < 128; i++) { int red = (int)(((((i & 0x60) >> 5) * 255) + 1.5) / 3); int green = (int)(((((i & 0x1C) >> 2) * 255) + 3.5) / 7); int blue = (int)((((i & 0x3) * 255) + 1.5) / 3); - write_data(self, (uint8_t []) {red, green, blue}, 3); + if (bgr) { + write_data(self, (uint8_t []) {blue, red, green}, 3); + } else { + write_data(self, (uint8_t []) {red, green, blue}, 3); + } } } else { for (int i = 0; i < 128; i++) { @@ -101,7 +139,8 @@ void shared_module_gifio_gifwriter_construct(gifio_gifwriter_t *self, mp_obj_t * write_data(self, (uint8_t []) {0x03, 0x01, 0x00, 0x00, 0x00}, 5); } - + flush_data(self); + handle_error(self); } bool shared_module_gifio_gifwriter_deinited(gifio_gifwriter_t *self) { @@ -163,10 +202,13 @@ void shared_module_gifio_gifwriter_add_frame(gifio_gifwriter_t *self, const mp_b block_data[0] = 1 + block_size; for (int j = 0; j < block_size; j++) { - int pixel = displayio_colorconverter_convert_pixel(self->colorspace, (*pixels++)); - int red = (pixel >> (16 + 6)) & 0x3; - int green = (pixel >> (8 + 5)) & 0x7; - int blue = (pixel >> 6) & 0x3; + int pixel = *pixels++; + if (self->byteswap) { + pixel = __builtin_bswap16(pixel); + } + int red = (pixel >> (11 + (5 - 2))) & 0x3; + int green = (pixel >> (5 + (6 - 3))) & 0x7; + int blue = (pixel >> (0 + (5 - 2))) & 0x3; block_data[j + 2] = (red << 5) | (green << 2) | blue; } write_data(self, block_data, 2 + block_size); @@ -174,25 +216,20 @@ void shared_module_gifio_gifwriter_add_frame(gifio_gifwriter_t *self, const mp_b } write_data(self, (uint8_t []) {0x01, 0x81, 0x00}, 3); // end code - - int error = 0; - self->file_proto->ioctl(self->file, MP_STREAM_FLUSH, 0, &error); - handle_error("flush", error); + flush_data(self); + handle_error(self); } void shared_module_gifio_gifwriter_close(gifio_gifwriter_t *self) { - // we want to ensure the stream is closed even if the first write failed, so we don't use write_data - int error1 = 0; - self->file_proto->write(self->file, ";", 1, &error1); + write_byte(self, ';'); + flush_data(self); - int error2 = 0; - if (self->own_file) { - self->file_proto->ioctl(self->file, MP_STREAM_CLOSE, 0, &error2); - } else { - self->file_proto->ioctl(self->file, MP_STREAM_FLUSH, 0, &error2); - } + int error = 0; + self->file_proto->ioctl(self->file, self->own_file ? MP_STREAM_CLOSE : MP_STREAM_FLUSH, 0, &error); self->file = NULL; - handle_error("write", error1); - handle_error("close", error2); + if (error != 0) { + self->error = error; + } + handle_error(self); } diff --git a/shared-module/gifio/GifWriter.h b/shared-module/gifio/GifWriter.h index e16de7c06b..e6676a84e0 100644 --- a/shared-module/gifio/GifWriter.h +++ b/shared-module/gifio/GifWriter.h @@ -36,5 +36,9 @@ typedef struct gifio_gifwriter { const mp_stream_p_t *file_proto; displayio_colorspace_t colorspace; int width, height; + int error; + uint8_t *data; + size_t cur, size; bool own_file; + bool byteswap; } gifio_gifwriter_t; From b881aec4c5c0b63532f48008d89e3945ccdf2a26 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Tue, 26 Oct 2021 14:24:11 -0500 Subject: [PATCH 07/10] disable gifio on meowbit --- ports/stm/boards/meowbit_v121/mpconfigboard.mk | 1 + 1 file changed, 1 insertion(+) diff --git a/ports/stm/boards/meowbit_v121/mpconfigboard.mk b/ports/stm/boards/meowbit_v121/mpconfigboard.mk index ed96e99871..589f0bce10 100644 --- a/ports/stm/boards/meowbit_v121/mpconfigboard.mk +++ b/ports/stm/boards/meowbit_v121/mpconfigboard.mk @@ -22,6 +22,7 @@ LD_FILE = boards/STM32F401xe_boot.ld CIRCUITPY_AESIO = 0 CIRCUITPY_BLEIO_HCI = 0 +CIRCUITPY_GIFIO = 0 CIRCUITPY_ULAB = 0 CIRCUITPY_STAGE = 1 From dc002261435272d9ca7b83f2516c3ac07957deef Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Wed, 27 Oct 2021 09:37:05 -0500 Subject: [PATCH 08/10] gifio: write block data directly into output buffer --- shared-module/gifio/GifWriter.c | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/shared-module/gifio/GifWriter.c b/shared-module/gifio/GifWriter.c index d5594538fe..5c3dbbfa24 100644 --- a/shared-module/gifio/GifWriter.c +++ b/shared-module/gifio/GifWriter.c @@ -175,8 +175,7 @@ void shared_module_gifio_gifwriter_add_frame(gifio_gifwriter_t *self, const mp_b int pixel_count = self->width * self->height; int blocks = (pixel_count + BLOCK_SIZE - 1) / BLOCK_SIZE; - uint8_t block_data[2 + BLOCK_SIZE]; - block_data[1] = 0x80; + uint8_t *data = self->data + self->cur; if (self->colorspace == DISPLAYIO_COLORSPACE_L8) { mp_get_index(&mp_type_memoryview, bufinfo->len, MP_OBJ_NEW_SMALL_INT(pixel_count - 1), false); @@ -186,11 +185,11 @@ void shared_module_gifio_gifwriter_add_frame(gifio_gifwriter_t *self, const mp_b assert(pixel_count >= 0); int block_size = MIN(BLOCK_SIZE, pixel_count); pixel_count -= block_size; - block_data[0] = 1 + block_size; + *data++ = 1 + block_size; + *data++ = 0x80; for (int j = 0; j < block_size; j++) { - block_data[j + 2] = (*pixels++) >> 1; + *data++ = (*pixels++) >> 1; } - write_data(self, block_data, 2 + block_size); } } else { mp_get_index(&mp_type_memoryview, bufinfo->len, MP_OBJ_NEW_SMALL_INT(2 * pixel_count - 1), false); @@ -200,7 +199,8 @@ void shared_module_gifio_gifwriter_add_frame(gifio_gifwriter_t *self, const mp_b int block_size = MIN(BLOCK_SIZE, pixel_count); pixel_count -= block_size; - block_data[0] = 1 + block_size; + *data++ = 1 + block_size; + *data++ = 0x80; for (int j = 0; j < block_size; j++) { int pixel = *pixels++; if (self->byteswap) { @@ -209,12 +209,13 @@ void shared_module_gifio_gifwriter_add_frame(gifio_gifwriter_t *self, const mp_b int red = (pixel >> (11 + (5 - 2))) & 0x3; int green = (pixel >> (5 + (6 - 3))) & 0x7; int blue = (pixel >> (0 + (5 - 2))) & 0x3; - block_data[j + 2] = (red << 5) | (green << 2) | blue; + *data++ = (red << 5) | (green << 2) | blue; } - write_data(self, block_data, 2 + block_size); } } + self->cur = data - self->data; + write_data(self, (uint8_t []) {0x01, 0x81, 0x00}, 3); // end code flush_data(self); handle_error(self); From ef4623dfae0b93de56c6106718f175faa7983c98 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Wed, 27 Oct 2021 09:37:47 -0500 Subject: [PATCH 09/10] gifio: Add dithered output It's not a great dither, but we're low on CPU time sooo --- shared-bindings/gifio/GifWriter.c | 10 ++++--- shared-bindings/gifio/GifWriter.h | 2 +- shared-module/gifio/GifWriter.c | 45 +++++++++++++++++++++++++++++-- shared-module/gifio/GifWriter.h | 1 + 4 files changed, 52 insertions(+), 6 deletions(-) diff --git a/shared-bindings/gifio/GifWriter.c b/shared-bindings/gifio/GifWriter.c index 6131550bf4..11dd43becd 100644 --- a/shared-bindings/gifio/GifWriter.c +++ b/shared-bindings/gifio/GifWriter.c @@ -34,24 +34,27 @@ #include "shared/runtime/context_manager_helpers.h" //| class GifWriter: -//| def __init__(self, file: Union[typing.BinaryIO, str], width:int, height:int, colorspace: displayio.Colorspace, loop:bool=True) -> None: +//| def __init__(self, file: Union[typing.BinaryIO, str], width:int, height:int, colorspace: displayio.Colorspace, loop:bool=True, dither:bool=False) -> None: //| """Construct a GifWriter object //| //| :param file: Either a file open in bytes mode, or the name of a file to open in bytes mode. //| :param width: The width of the image. All frames must have the same width. //| :param height: The height of the image. All frames must have the same height. -//| :param colorspace: The colorspace of the image. All frames must have the same colorspace. Only 1- and 2-byte colorspace are supported, not ``RGB888``. +//| :param colorspace: The colorspace of the image. All frames must have the same colorspace. The supported colorspaces are ``RGB565``, ``BGR565``, ``RGB565_SWAPPED``, ``BGR565_SWAPPED``, and ``L8`` (greyscale) +//| :param loop: If True, the GIF is marked for looping playback +//| :param dither: If True, and the image is in color, a simple ordered dither is applied. //| """ //| ... //| static mp_obj_t gifio_gifwriter_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { - enum { ARG_file, ARG_width, ARG_height, ARG_colorspace, ARG_loop }; + enum { ARG_file, ARG_width, ARG_height, ARG_colorspace, ARG_loop, ARG_dither }; static const mp_arg_t allowed_args[] = { { MP_QSTR_file, MP_ARG_OBJ | MP_ARG_REQUIRED, {.u_obj = NULL} }, { MP_QSTR_width, MP_ARG_INT | MP_ARG_REQUIRED, {.u_int = 0} }, { MP_QSTR_height, MP_ARG_INT | MP_ARG_REQUIRED, {.u_int = 0} }, { MP_QSTR_colorspace, MP_ARG_OBJ | MP_ARG_REQUIRED, {.u_obj = NULL} }, { MP_QSTR_loop, MP_ARG_BOOL, { .u_bool = true } }, + { MP_QSTR_dither, MP_ARG_BOOL, { .u_bool = false } }, }; mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); @@ -72,6 +75,7 @@ static mp_obj_t gifio_gifwriter_make_new(const mp_obj_type_t *type, size_t n_arg args[ARG_height].u_int, (displayio_colorspace_t)cp_enum_value(&displayio_colorspace_type, args[ARG_colorspace].u_obj), args[ARG_loop].u_bool, + args[ARG_dither].u_bool, own_file); return self; diff --git a/shared-bindings/gifio/GifWriter.h b/shared-bindings/gifio/GifWriter.h index 203169cd75..601bd78679 100644 --- a/shared-bindings/gifio/GifWriter.h +++ b/shared-bindings/gifio/GifWriter.h @@ -33,7 +33,7 @@ typedef enum displayio_colorspace displayio_colorspace_t; extern const mp_obj_type_t gifio_gifwriter_type; -void shared_module_gifio_gifwriter_construct(gifio_gifwriter_t *self, mp_obj_t *file, int width, int height, displayio_colorspace_t colorspace, bool loop, bool own_file); +void shared_module_gifio_gifwriter_construct(gifio_gifwriter_t *self, mp_obj_t *file, int width, int height, displayio_colorspace_t colorspace, bool loop, bool dither, bool own_file); void shared_module_gifio_gifwriter_check_for_deinit(gifio_gifwriter_t *self); bool shared_module_gifio_gifwriter_deinited(gifio_gifwriter_t *self); void shared_module_gifio_gifwriter_deinit(gifio_gifwriter_t *self); diff --git a/shared-module/gifio/GifWriter.c b/shared-module/gifio/GifWriter.c index 5c3dbbfa24..608e522517 100644 --- a/shared-module/gifio/GifWriter.c +++ b/shared-module/gifio/GifWriter.c @@ -76,7 +76,7 @@ static void write_word(gifio_gifwriter_t *self, uint16_t value) { write_data(self, &value, sizeof(value)); } -void shared_module_gifio_gifwriter_construct(gifio_gifwriter_t *self, mp_obj_t *file, int width, int height, displayio_colorspace_t colorspace, bool loop, bool own_file) { +void shared_module_gifio_gifwriter_construct(gifio_gifwriter_t *self, mp_obj_t *file, int width, int height, displayio_colorspace_t colorspace, bool loop, bool dither, bool own_file) { self->file = file; self->file_proto = mp_proto_get_or_throw(MP_QSTR_protocol_stream, file); if (self->file_proto->is_text) { @@ -85,6 +85,7 @@ void shared_module_gifio_gifwriter_construct(gifio_gifwriter_t *self, mp_obj_t * self->width = width; self->height = height; self->colorspace = colorspace; + self->dither = dither; self->own_file = own_file; size_t nblocks = (width * height + 125) / 126; @@ -159,6 +160,20 @@ void shared_module_gifio_gifwriter_deinit(gifio_gifwriter_t *self) { } } +static const uint8_t rb_bayer[4][4] = { + { 0, 33, 8, 42}, + {50, 16, 58, 25}, + {12, 46, 4, 37}, + {63, 29, 54, 21} +}; + +static const uint8_t g_bayer[4][4] = { + { 0, 16, 4, 20}, + {24, 8, 28, 12}, + { 6, 22, 2, 18}, + {31, 14, 26, 10} +}; + void shared_module_gifio_gifwriter_add_frame(gifio_gifwriter_t *self, const mp_buffer_info_t *bufinfo, int16_t delay) { if (delay) { write_data(self, (uint8_t []) {'!', 0xF9, 0x04, 0x04}, 4); @@ -191,7 +206,7 @@ void shared_module_gifio_gifwriter_add_frame(gifio_gifwriter_t *self, const mp_b *data++ = (*pixels++) >> 1; } } - } else { + } else if (!self->dither) { mp_get_index(&mp_type_memoryview, bufinfo->len, MP_OBJ_NEW_SMALL_INT(2 * pixel_count - 1), false); uint16_t *pixels = bufinfo->buf; @@ -212,6 +227,32 @@ void shared_module_gifio_gifwriter_add_frame(gifio_gifwriter_t *self, const mp_b *data++ = (red << 5) | (green << 2) | blue; } } + } else { + mp_get_index(&mp_type_memoryview, bufinfo->len, MP_OBJ_NEW_SMALL_INT(2 * pixel_count - 1), false); + + uint16_t *pixels = bufinfo->buf; + for (int i = 0; i < blocks; i++) { + int block_size = MIN(BLOCK_SIZE, pixel_count); + pixel_count -= block_size; + + *data++ = 1 + block_size; + *data++ = 0x80; + for (int j = 0; j < block_size; j++) { + int pixel = *pixels++; + if (self->byteswap) { + pixel = __builtin_bswap16(pixel); + } + int red = (pixel >> 8) & 0xf8; + int green = (pixel >> 3) & 0xfc; + int blue = (pixel << 3) & 0xf8; + + red = MAX(0, red - rb_bayer[i % 4][j % 4]); + green = MAX(0, green - g_bayer[i % 4][j % 4]); + blue = MAX(0, blue - rb_bayer[i % 4][j % 4]); + + *data++ = ((red >> 1) & 0x60) | ((green >> 3) & 0x1c) | (blue >> 6); + } + } } self->cur = data - self->data; diff --git a/shared-module/gifio/GifWriter.h b/shared-module/gifio/GifWriter.h index e6676a84e0..5ed57bb022 100644 --- a/shared-module/gifio/GifWriter.h +++ b/shared-module/gifio/GifWriter.h @@ -41,4 +41,5 @@ typedef struct gifio_gifwriter { size_t cur, size; bool own_file; bool byteswap; + bool dither; } gifio_gifwriter_t; From e7338765742e3ddec6da1305a48b4d89f1d2928d Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Wed, 27 Oct 2021 16:52:48 -0500 Subject: [PATCH 10/10] dither in x/y, not i/j The easiest thing to implement was to use the i/j numbers, but they were not directly related to image x/y coordinates. This may slow things down a tiny little bit, but it looks much better. --- shared-module/gifio/GifWriter.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/shared-module/gifio/GifWriter.c b/shared-module/gifio/GifWriter.c index 608e522517..f8c2c7d3ce 100644 --- a/shared-module/gifio/GifWriter.c +++ b/shared-module/gifio/GifWriter.c @@ -231,6 +231,7 @@ void shared_module_gifio_gifwriter_add_frame(gifio_gifwriter_t *self, const mp_b mp_get_index(&mp_type_memoryview, bufinfo->len, MP_OBJ_NEW_SMALL_INT(2 * pixel_count - 1), false); uint16_t *pixels = bufinfo->buf; + int x = 0, y = 0; for (int i = 0; i < blocks; i++) { int block_size = MIN(BLOCK_SIZE, pixel_count); pixel_count -= block_size; @@ -246,9 +247,14 @@ void shared_module_gifio_gifwriter_add_frame(gifio_gifwriter_t *self, const mp_b int green = (pixel >> 3) & 0xfc; int blue = (pixel << 3) & 0xf8; - red = MAX(0, red - rb_bayer[i % 4][j % 4]); - green = MAX(0, green - g_bayer[i % 4][j % 4]); - blue = MAX(0, blue - rb_bayer[i % 4][j % 4]); + red = MAX(0, red - rb_bayer[x % 4][y % 4]); + green = MAX(0, green - g_bayer[x % 4][(y + 2) % 4]); + blue = MAX(0, blue - rb_bayer[(x + 2) % 4][y % 4]); + x++; + if (x == self->width) { + x = 0; + y++; + } *data++ = ((red >> 1) & 0x60) | ((green >> 3) & 0x1c) | (blue >> 6); }