# The MIT License (MIT) # # Copyright (c) 2019 Dave Astels 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. """ `adafruit_bitmapsaver` ================================================================================ Save a displayio.Bitmap (and associated displayio.Palette) in a BMP file. Make a screenshot (the contents of a displayio.Display) and save in a BMP file. * Author(s): Dave Astels Implementation Notes -------------------- **Hardware:** **Software and Dependencies:** * Adafruit CircuitPython firmware for the supported boards: https://github.com/adafruit/circuitpython/releases """ # imports import gc import struct import board from displayio import Bitmap, Palette, Display __version__ = "0.0.0-auto.0" __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_BitmapSaver.git" def _write_bmp_header(output_file, filesize): output_file.write(bytes("BM", "ascii")) output_file.write(struct.pack("> 3) & 0x00FC red = (color >> 8) & 0x00F8 return (blue, green, red) # pylint:disable=too-many-locals def _write_pixels(output_file, pixel_source, palette): saving_bitmap = isinstance(pixel_source, Bitmap) width, height = _rotated_height_and_width(pixel_source) row_buffer = bytearray(_bytes_per_row(width)) result_buffer = bytearray(2048) for y in range(height, 0, -1): buffer_index = 0 if saving_bitmap: for x in range(width): pixel = pixel_source[x, y - 1] color = palette[pixel] for _ in range(3): row_buffer[buffer_index] = color & 0xFF color >>= 8 buffer_index += 1 else: data = pixel_source.fill_row(y - 1, result_buffer) for i in range(width): pixel565 = (data[i * 2] << 8) + data[i * 2 + 1] for b in _rgb565_to_bgr_tuple(pixel565): row_buffer[buffer_index] = b & 0xFF buffer_index += 1 output_file.write(row_buffer) gc.collect() # pylint:enable=too-many-locals def save_pixels(file_or_filename, pixel_source=None, palette=None): """Save pixels to a 24 bit per pixel BMP file. If pixel_source if a displayio.Bitmap, save it's pixels through palette. If it's a displayio.Display, a palette isn't required. :param file_or_filename: either the file to save to, or it's absolute name :param pixel_source: the Bitmap or Display to save :param palette: the Palette to use for looking up colors in the bitmap """ if not pixel_source: if "DISPLAY" in dir(board): pixel_source = board.DISPLAY else: raise ValueError("Second argument must be a Bitmap or Display") if isinstance(pixel_source, Bitmap): if not isinstance(palette, Palette): raise ValueError("Third argument must be a Palette for a Bitmap save") elif not isinstance(pixel_source, Display): raise ValueError("Second argument must be a Bitmap or Display") try: if isinstance(file_or_filename, str): output_file = open(file_or_filename, "wb") else: output_file = file_or_filename width, height = _rotated_height_and_width(pixel_source) filesize = 54 + height * _bytes_per_row(width) _write_bmp_header(output_file, filesize) _write_dib_header(output_file, width, height) _write_pixels(output_file, pixel_source, palette) except Exception as ex: raise ex else: output_file.close()