Source code for luma.oled.device.color

# -*- coding: utf-8 -*-
# Copyright (c) 2018-2022 Richard Hull and contributors
# See LICENSE.rst for details.

"""
Base class for SSD13xx color devices

.. versionadded:: 3.0.0
"""

from abc import abstractmethod, ABCMeta

from luma.core.device import device
import luma.core.error
import luma.core.framebuffer
import luma.oled.const
from luma.oled.device.framebuffer_mixin import __framebuffer_mixin


class color_device(device, __framebuffer_mixin):
    __metaclass__ = ABCMeta

    def __init__(self, serial_interface, width, height, rotate, framebuffer, **kwargs):
        super(color_device, self).__init__(luma.oled.const.common, serial_interface)
        self.capabilities(width, height, rotate, mode="RGB")
        self.init_framebuffer(framebuffer)

        if (width, height) not in self._supported_dimensions():
            raise luma.core.error.DeviceDisplayModeError(
                f"Unsupported display mode: {width} x {height}")

        self._init_sequence()
        self.contrast(0xFF)
        self.clear()
        self.show()

    @abstractmethod
    def _supported_dimensions(self):
        """
        Enumerates the screen resolutions that the specific device supports, as
        a list of tuples; e.g.: ``[(96, 64), (96, 32), (96, 16)]``
        """
        pass  # pragma: no cover

    @abstractmethod
    def _init_sequence(self):
        """
        Concrete implementations should call the initiation sequence for the
        specific device. Invoked from the constructor, but no assumptions should
        be made about what has been initialized so far. No return value is
        expected.
        """
        pass  # pragma: no cover

    @abstractmethod
    def _set_position(self, top, right, bottom, left):
        """
        Invoked once as part of the devices display refresh. The four coordinates
        form a bounding box that determines the area of the screen that will get
        get redrawn; thus the concrete implementations should send the correct
        command sequence to the device to set that bounding box. No return value
        is expected.
        """
        pass  # pragma: no cover

    def _apply_offsets(self, bbox):
        return bbox

    def display(self, image):
        """
        Renders a 24-bit RGB image to the Color OLED display.

        :param image: The image to render.
        :type image: PIL.Image.Image
        """
        assert image.mode == self.mode
        assert image.size == self.size

        image = self.preprocess(image)

        for image, bounding_box in self.framebuffer.redraw(image):
            left, top, right, bottom = self._apply_offsets(bounding_box)
            width = right - left
            height = bottom - top

            self._set_position(top, right, bottom, left)

            i = 0
            buf = bytearray(width * height * 2)
            for r, g, b in image.getdata():
                if not r == g == b == 0:
                    # 65K format 1
                    buf[i] = r & 0xF8 | g >> 5
                    buf[i + 1] = g << 3 & 0xE0 | b >> 3
                i += 2

            self.data(list(buf))