kraken3: add LCD screen support for Kraken Z coolers (#479)

Closes: #444
This commit is contained in:
Shady Nawara 2022-09-17 23:22:45 -05:00 committed by GitHub
parent a2b4391408
commit f455e3f446
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 1022 additions and 178 deletions

View File

@ -62,7 +62,9 @@ jobs:
hidapi \
pytest \
pyusb \
pillow \
"libusb-package; sys_platform == 'win32' or sys_platform == 'cygwin'" \
"winusbcdc; sys_platform == 'win32'" \
"smbus; sys_platform == 'linux'"
- name: Run unit tests and module doctests

View File

@ -123,7 +123,7 @@ subjective "from more to less liquid control-ly" order.
| AIO liquid cooler | [NZXT Kraken X31, X41, X61](docs/asetek-690lc-guide.md) | USB | <sup>_LZ_</sup> |
| AIO liquid cooler | [NZXT Kraken X42, X52, X62, X72](docs/kraken-x2-m2-guide.md) | USB HID | <sup>_h_</sup> |
| AIO liquid cooler | [NZXT Kraken X53, X63, X73](docs/kraken-x3-z3-guide.md) | USB HID | <sup>_h_</sup> |
| AIO liquid cooler | [NZXT Kraken Z53, Z63, Z73](docs/kraken-x3-z3-guide.md) | USB & USB HID | <sup>_p_</sup> |
| AIO liquid cooler | [NZXT Kraken Z53, Z63, Z73](docs/kraken-x3-z3-guide.md) | USB & USB HID | |
| Pump controller | [Aquacomputer D5 Next](docs/aquacomputer-d5next-guide.md) | USB HID | <sup>_ehnp_</sup> |
| Fan/LED controller | [Aquacomputer Octo](docs/aquacomputer-octo-guide.md) | USB HID | <sup>_ehnp_</sup> |
| Fan/LED controller | [Aquacomputer Quadro](docs/aquacomputer-quadro-guide.md) | USB HID | <sup>_ehnp_</sup> |
@ -406,9 +406,14 @@ already installed on the environment (virtual or global), manually install
them:
```
python -m pip install --upgrade colorlog docopt hidapi pytest pyusb setuptools setuptools_scm
python -m pip install --upgrade colorlog docopt hidapi pytest pyusb setuptools setuptools_scm pillow
python -m pip install --upgrade "libusb-package; sys_platform == 'win32' or sys_platform == 'cygwin'"
python -m pip install --upgrade "smbus; sys_platform == 'linux'"
```
For KrakenZ devices only
```
python -m pip install --upgrade "winusbcdc; sys_platform == 'win32'"
```
At this point, the environment is set up. To run the test suite, execute:

View File

@ -21,8 +21,6 @@ The most notable difference between Kraken X and Kraken Z models is the replacem
In addition to this, Kraken Z coolers restore the embedded fan controller that is missing from the current Kraken X models.
The LCD screen cannot yet be controlled with liquidctl, but all other hardware capabilities are supported.
## Initialization
@ -158,8 +156,16 @@ they will be removed in a future version and are kept for now for backward compa
## The LCD screen (only Z models)
To be implemented.
Liquidctl supports the following functions for controlling the lcd screen
```
liquidctl [options] set lcd screen liquid
liquidctl [options] set lcd screen brightness <value>
liquidctl [options] set lcd screen orientation (0|90|180|270)
liquidctl [options] set lcd screen (static|gif) <path to image>
```
images and GiFs are automatically resized and rotated to match the device orientation so there is no need for any preprocessing
## Interaction with Linux hwmon drivers
[Linux hwmon]: #interaction-with-linux-hwmon-drivers

View File

@ -7,6 +7,7 @@ Usage:
liquidctl [options] set <channel> speed (<temperature> <percentage>) ...
liquidctl [options] set <channel> speed <percentage>
liquidctl [options] set <channel> color <mode> [<color>] ...
liquidctl [options] set <channel> screen <mode> [<value>]
liquidctl --help
liquidctl --version
@ -279,6 +280,8 @@ def _device_set_color(dev, args, **opts):
color = map(color_from_str, args['<color>'])
dev.set_color(args['<channel>'].lower(), args['<mode>'].lower(), color, **opts)
def _device_set_screen(dev, args, **opts):
dev.set_screen(args["<channel>"], args["<mode>"], args["<value>"], **opts)
def _device_set_speed(dev, args, **opts):
if len(args['<temperature>']) > 0:
@ -468,6 +471,8 @@ def main():
_device_set_speed(dev, args, **opts)
elif args['set'] and args['color']:
_device_set_color(dev, args, **opts)
elif args['set'] and args['screen']:
_device_set_screen(dev, args, **opts)
else:
assert False, 'unreachable'
except OSError as err:

View File

@ -186,6 +186,10 @@ class _Base690Lc(UsbDriver):
super().disconnect(**kwargs)
def set_screen(self, channel, mode, value, **kwargs):
"""Not supported by this device."""
raise NotSupportedByDevice()
class _ModernBase690Lc(_Base690Lc):
@ -427,7 +431,7 @@ class Legacy690Lc(_Base690Lc):
def set_speed_profile(self, channel, profile, **kwargs):
"""Not supported by this device."""
raise NotSupportedByDevice
raise NotSupportedByDevice()
class Hydro690Lc(_ModernBase690Lc):

View File

@ -257,6 +257,10 @@ class HydroPro(_Base690Lc):
else:
raise ValueError(f'unknown channel: {channel}')
def set_screen(self, channel, mode, value, **kwargs):
"""Not supported by this device."""
raise NotSupportedByDevice()
@classmethod
def probe(cls, handle, **kwargs):
return super().probe(handle, **kwargs)

View File

@ -343,3 +343,7 @@ class AuraLed(UsbHidDriver):
def _write(self, data):
padding = [0x0] * (_WRITE_LENGTH - len(data))
self.device.write(data + padding)
def set_screen(self, channel, mode, value, **kwargs):
"""Not supported by this device."""
raise NotSupportedByDevice()

View File

@ -92,6 +92,11 @@ class BaseDriver:
raise NotImplementedError()
def set_screen(self, channel, mode, value, **kwargs):
"""Set the screen mode and content."""
raise NotImplementedError()
def set_speed_profile(self, channel, profile, **kwargs):
"""Set channel to follow a speed duty profile."""

View File

@ -12,7 +12,7 @@ import logging
from contextlib import contextmanager
from liquidctl.driver.usb import UsbHidDriver
from liquidctl.error import ExpectationNotMet, NotSupportedByDriver
from liquidctl.error import ExpectationNotMet, NotSupportedByDriver, NotSupportedByDevice
from liquidctl.util import clamp, u16le_from
_LOGGER = logging.getLogger(__name__)
@ -274,3 +274,7 @@ class CommanderCore(UsbHidDriver):
if self._has_pump:
fan_names.insert(0, "pump")
raise ValueError(f'unknown channel, should be one of: "{fan_names_part}" or "fans"')
def set_screen(self, channel, mode, value, **kwargs):
"""Not supported by this device."""
raise NotSupportedByDevice

View File

@ -561,3 +561,7 @@ class CommanderPro(UsbHidDriver):
self.device.write(buf)
buf = bytes(self.device.read(_RESPONSE_LENGTH))
return buf
def set_screen(self, channel, mode, value, **kwargs):
"""Not supported by this device."""
raise NotSupportedByDevice()

View File

@ -296,6 +296,10 @@ class CorsairHidPsu(UsbHidDriver):
secs = int.from_bytes(self._exec(WriteBit.READ, command)[2:], byteorder='little')
return timedelta(seconds=secs)
def set_screen(self, channel, mode, value, **kwargs):
"""Not supported by this device."""
raise NotSupportedByDevice()
# deprecated aliases
CorsairHidPsuDriver = CorsairHidPsu

View File

@ -261,6 +261,10 @@ class Ddr4Temperature(SmbusDriver):
"""Not supported by this device."""
raise NotSupportedByDevice()
def set_screen(self, channel, mode, value, **kwargs):
"""Not supported by this device."""
raise NotSupportedByDevice()
class VengeanceRgb(Ddr4Temperature):
"""Corsair Vengeance RGB DDR4 module."""

View File

@ -20,6 +20,7 @@ import re
from enum import Enum, unique
from liquidctl.driver.usb import UsbHidDriver
from liquidctl.error import NotSupportedByDevice
from liquidctl.keyval import RuntimeStorage
from liquidctl.pmbus import compute_pec
from liquidctl.util import RelaxedNamesEnum, clamp, fraction_of_byte, \
@ -420,3 +421,7 @@ class HydroPlatinum(UsbHidDriver):
self._send_command(_FEATURE_COOLING2, _CMD_SET_COOLING, data=data2)
return self._send_command(_FEATURE_COOLING, _CMD_SET_COOLING, data=data)
def set_screen(self, channel, mode, value, **kwargs):
"""Not supported by this device."""
raise NotSupportedByDevice()

View File

@ -291,6 +291,10 @@ class Kraken2(UsbHidDriver):
padding = [0x0]*(_WRITE_LENGTH - len(data))
self.device.write(data + padding)
def set_screen(self, channel, mode, value, **kwargs):
"""Not supported by this device."""
raise NotSupportedByDevice()
# deprecated aliases
KrakenTwoDriver = Kraken2

File diff suppressed because it is too large Load Diff

View File

@ -304,6 +304,10 @@ class EvgaPascal(SmbusDriver, _NvidiaI2CDriver):
"""Not supported by this device."""
raise NotSupportedByDevice()
def set_screen(self, channel, mode, value, **kwargs):
"""Not supported by this device."""
raise NotSupportedByDevice()
class RogTuring(SmbusDriver, _NvidiaI2CDriver):
"""NVIDIA series 10 (Pascal) or 20 (Turing) graphics card from ASUS."""
@ -533,3 +537,7 @@ class RogTuring(SmbusDriver, _NvidiaI2CDriver):
def set_fixed_speed(self, channel, duty, **kwargs):
"""Not supported by this device."""
raise NotSupportedByDevice()
def set_screen(self, channel, mode, value, **kwargs):
"""Not supported by this device."""
raise NotSupportedByDevice()

View File

@ -145,6 +145,10 @@ class NzxtEPsu(UsbHidDriver):
ascam_ver = int.from_bytes(bytes.fromhex(human_ver), byteorder='big')
return (human_ver, ascam_ver)
def set_screen(self, channel, mode, value, **kwargs):
"""Not supported by this device."""
raise NotSupportedByDevice()
# deprecated aliases
SeasonicEDriver = NzxtEPsu

View File

@ -243,6 +243,10 @@ class RgbFusion2(UsbHidDriver):
"""Request for the previously sent lighting settings to be applied."""
self._send_feature_report([_REPORT_ID, 0x28, 0xff])
def set_screen(self, channel, mode, value, **kwargs):
"""Not supported by this device."""
raise NotSupportedByDevice()
# Acknowledgments:
#

View File

@ -198,6 +198,10 @@ class _BaseSmartDevice(UsbHidDriver):
def _write_fixed_duty(self, cid, duty):
raise NotImplementedError()
def set_screen(self, channel, mode, value, **kwargs):
"""Not supported by this device."""
raise NotSupportedByDevice()
class SmartDevice(_BaseSmartDevice):
"""NZXT Smart Device (V1) or Grid+ V3."""

View File

@ -41,7 +41,6 @@ extend-exclude = '''
| (^/liquidctl/driver/ddr4\.py$)
| (^/liquidctl/driver/hydro_platinum\.py$)
| (^/liquidctl/driver/kraken2\.py$)
| (^/liquidctl/driver/kraken3\.py$)
| (^/liquidctl/driver/nvidia\.py$)
| (^/liquidctl/driver/nzxt_epsu\.py$)
| (^/liquidctl/driver/rgb_fusion2\.py$)
@ -72,6 +71,7 @@ extend-exclude = '''
| (^/tests/test_hydro_platinum\.py$)
| (^/tests/test_keyval\.py$)
| (^/tests/test_kraken2\.py$)
| (^/tests/test_krakenz3_response\.py$)
| (^/tests/test_nvidia\.py$)
| (^/tests/test_nzxt_epsu\.py$)
| (^/tests/test_rgb_fusion2\.py$)

View File

@ -47,7 +47,9 @@ install_requires =
docopt
hidapi
pyusb
pillow
libusb-package; sys_platform == 'win32' or sys_platform == 'cygwin'
winusbcdc; sys_platform == 'win32'
smbus; sys_platform == "linux"
[options.entry_points]

BIN
tests/rgb.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

View File

@ -1,7 +1,9 @@
# uses the psf/black style
import pytest
from _testutils import MockHidapiDevice, Report
import os
from _testutils import MockHidapiDevice, MockPyusbDevice, Report
from liquidctl.driver.hwmon import HwmonDevice
from liquidctl.driver.kraken3 import KrakenX3, KrakenZ3
@ -11,6 +13,8 @@ from liquidctl.driver.kraken3 import (
_COLOR_CHANNELS_KRAKENZ,
_SPEED_CHANNELS_KRAKENZ,
)
from test_krakenz3_response import krakenz3_response
from liquidctl.util import HUE2_MAX_ACCESSORIES_IN_CHANNEL as MAX_ACCESSORIES
from liquidctl.util import Hue2Accessory
@ -44,7 +48,7 @@ def mock_krakenx3():
@pytest.fixture
def mock_krakenz3():
raw = MockKraken(raw_led_channels=1)
dev = KrakenZ3(
dev = MockKrakenZ3(
raw,
"Mock Kraken Z73",
speed_channels=_SPEED_CHANNELS_KRAKENZ,
@ -70,10 +74,74 @@ class MockKraken(MockHidapiDevice):
if self.raw_led_channels > 1:
reply[15 + 1 * MAX_ACCESSORIES] = Hue2Accessory.KRAKENX_GEN4_RING.value
reply[15 + 2 * MAX_ACCESSORIES] = Hue2Accessory.KRAKENX_GEN4_LOGO.value
elif data[0:2] == [0x30, 0x01]:
reply[0:2] = [0x31, 0x01]
reply[0x18] = 50 # lcd brightness
reply[0x1A] = 0 # lcd orientation
elif data[0:2] == [0x32, 0x1]: # setup bucket
reply[14] = 0x1
elif data[0:2] == [0x32, 0x2]: # delete bucker
reply[0:2] = [0x33, 0x02]
reply[14] = 0x1
elif data[0:2] == [0x38, 0x1]: # switch bucket
reply[14] = 0x1
self.preload_read(Report(0, reply))
return super().write(data)
class MockKrakenZ3(KrakenZ3):
def __init__(self, device, description, speed_channels, color_channels, **kwargs):
KrakenX3.__init__(self, device, description, speed_channels, color_channels, **kwargs)
self.bulk_device = MockPyusbDevice(0x1E71, 0x3008)
self.bulk_device.close_winusb_device = self.bulk_device.release
self.orientation = 0
self.brightness = 50
self.screen_mode = None
def set_screen(self, channel, mode, value, **kwargs):
self.screen_mode = mode
self.hid_data_index = 0
self.bulk_data_index = 0
super().set_screen(channel, mode, value, **kwargs)
assert self.hid_data_index == len(
krakenz3_response[self.screen_mode + "_hid"]
), f"Incorrect number of hid messages sent for mode: {mode}"
if mode == "static" or mode == "gif":
assert (
self.bulk_data_index == 801
if mode == "static"
else len(krakenz3_response[self.screen_mode + "_bulk"])
), f"Incorrect number of bulk messages sent for mode: {mode}"
def _write(self, data):
if self.screen_mode:
assert (
data == krakenz3_response[self.screen_mode + "_hid"][self.hid_data_index]
), f"HID write failed, wrong data for mode: {self.screen_mode}, data index: {self.hid_data_index}"
self.hid_data_index += 1
return super()._write(data)
def _bulk_write(self, data):
fixed_data_index = self.bulk_data_index
if (
self.screen_mode == "static" and self.bulk_data_index > 1
): # the rest of the message should be identical to index 1
fixed_data_index = 1
assert (
data == krakenz3_response[self.screen_mode + "_bulk"][fixed_data_index]
), f"Bulk write failed, wrong data for mode: {self.screen_mode}, data index: {self.bulk_data_index}"
self.bulk_data_index += 1
return super()._bulk_write(data)
@pytest.mark.parametrize("has_hwmon,direct_access", [(False, False), (True, True), (True, False)])
def test_krakenx3_initializes(mock_krakenx3, has_hwmon, direct_access, tmp_path):
if has_hwmon:
@ -142,3 +210,14 @@ def test_krakenz3_not_totally_broken(mock_krakenz3):
_ = mock_krakenz3.get_status()
mock_krakenz3.set_speed_profile(channel="fan", profile=iter([(20, 20), (30, 50), (40, 100)]))
mock_krakenz3.set_fixed_speed(channel="pump", duty=50)
# set_screen should be the last set of functions called
mock_krakenz3.set_screen("lcd", "liquid", None)
mock_krakenz3.set_screen("lcd", "brightness", "60")
mock_krakenz3.set_screen("lcd", "orientation", "90")
mock_krakenz3.set_screen(
"lcd", "static", os.path.join(os.path.dirname(os.path.abspath(__file__)), "yellow.jpg")
)
mock_krakenz3.set_screen(
"lcd", "gif", os.path.join(os.path.dirname(os.path.abspath(__file__)), "rgb.gif")
)

View File

@ -0,0 +1,70 @@
krakenz3_response = {
"liquid_hid": [[48, 1], [56, 1, 2, 0]],
"brightness_hid": [[48, 1], [48, 2, 1, 60, 0, 0, 1, 0]],
"orientation_hid": [[48, 1], [48, 2, 1, 50, 0, 0, 1, 1]],
"static_hid": [
[48, 1],
[54, 3],
[48, 4, 0],
[48, 4, 1],
[48, 4, 2],
[48, 4, 3],
[48, 4, 4],
[48, 4, 5],
[48, 4, 6],
[48, 4, 7],
[48, 4, 8],
[48, 4, 9],
[48, 4, 10],
[48, 4, 11],
[48, 4, 12],
[48, 4, 13],
[48, 4, 14],
[48, 4, 15],
[32, 3],
[116, 1],
[112, 1],
[116, 1],
[50, 2, 0],
[50, 1, 0, 1, 0, 0, 144, 1, 1],
[54, 1, 0],
[54, 2],
[56, 1, 4, 0],
],
"gif_hid": [[48,1],
[54,3],
[48,4,0],
[48,4,1],
[48,4,2],
[48,4,3],
[48,4,4],
[48,4,5],
[48,4,6],
[48,4,7],
[48,4,8],
[48,4,9],
[48,4,10],
[48,4,11],
[48,4,12],
[48,4,13],
[48,4,14],
[48,4,15],
[32,3],
[116,1],
[112,1],
[116,1],
[50,2,0],
[50,1,0,1,0,0,2,0,1],
[54,1,0],
[54,2],
[56,1,4,0]],
"static_bulk": [
[18,250,1,232,171,205,239,152,118,84,50,16,2,0,0,0,0,64,6],
[255,255,1,0,255,255,1,0,255,255,1,0,255,255,1,0,255,255,1,0,255,255,1,0,255,255,1,0,255,255,1,0,255,255,1,0,255,255,1,0,255,255,1,0,255,255,1,0,255,255,1,0,255,255,1,0,255,255,1,0,255,255,1,0,255,255,1,0,255,255,1,0,255,255,1,0,255,255,1,0,255,255,1,0,255,255,1,0,255,255,1,0,255,255,1,0,255,255,1,0,255,255,1,0,255,255,1,0,255,255,1,0,255,255,1,0,255,255,1,0,255,255,1,0,255,255,1,0,255,255,1,0,255,255,1,0,255,255,1,0,255,255,1,0,255,255,1,0,255,255,1,0,255,255,1,0,255,255,1,0,255,255,1,0,255,255,1,0,255,255,1,0,255,255,1,0,255,255,1,0,255,255,1,0,255,255,1,0,255,255,1,0,255,255,1,0,255,255,1,0,255,255,1,0,255,255,1,0,255,255,1,0,255,255,1,0,255,255,1,0,255,255,1,0,255,255,1,0,255,255,1,0,255,255,1,0,255,255,1,0,255,255,1,0,255,255,1,0,255,255,1,0,255,255,1,0,255,255,1,0,255,255,1,0,255,255,1,0,255,255,1,0,255,255,1,0,255,255,1,0,255,255,1,0,255,255,1,0,255,255,1,0,255,255,1,0,255,255,1,0,255,255,1,0,255,255,1,0,255,255,1,0,255,255,1,0,255,255,1,0,255,255,1,0,255,255,1,0,255,255,1,0,255,255,1,0,255,255,1,0,255,255,1,0,255,255,1,0,255,255,1,0,255,255,1,0,255,255,1,0,255,255,1,0,255,255,1,0,255,255,1,0,255,255,1,0,255,255,1,0,255,255,1,0,255,255,1,0,255,255,1,0,255,255,1,0,255,255,1,0,255,255,1,0,255,255,1,0,255,255,1,0,255,255,1,0,255,255,1,0,255,255,1,0,255,255,1,0,255,255,1,0,255,255,1,0,255,255,1,0,255,255,1,0,255,255,1,0,255,255,1,0,255,255,1,0,255,255,1,0,255,255,1,0,255,255,1,0,255,255,1,0,255,255,1,0,255,255,1,0,255,255,1,0,255,255,1,0,255,255,1,0,255,255,1,0,255,255,1,0,255,255,1,0,255,255,1,0,255,255,1,0]],
"gif_bulk": [
[18,250,1,232,171,205,239,152,118,84,50,16,1,0,0,0,189,6,0],
[71,73,70,56,57,97,64,1,64,1,129,3,0,255,0,0,0,0,0,0,0,0,0,0,0,33,255,11,78,69,84,83,67,65,80,69,50,46,48,3,1,0,0,0,44,0,0,0,0,64,1,64,1,0,8,255,0,1,8,28,72,176,160,193,131,8,19,42,92,200,176,161,195,135,16,35,74,156,72,177,162,197,139,24,51,106,220,200,177,163,199,143,32,67,138,28,73,178,164,201,147,40,83,170,92,201,178,165,203,151,48,99,202,156,73,179,166,205,155,56,115,234,220,201,179,167,207,159,64,131,10,29,74,180,168,209,163,72,147,42,93,202,180,169,211,167,80,163,74,157,74,181,170,213,171,88,179,106,221,202,181,171,215,175,96,195,138,29,75,182,172,217,179,104,211,170,93,203,182,173,219,183,112,227,202,157,75,183,174,221,187,120,243,234,221,203,183,175,223,191,128,3,11,30,76,184,176,225,195,136,19,43,94,204,184,177,227,199,144,35,75,158,76,185,178,229,203,152,51,107,222,204,185,179,231,207,160,67,139,30,77,186,180,233,211,168,83,171,94,205,186,181,235,215,176,99,203,158,77,187,182,237,219,184,115,235,222,205,187,183,239,223,192,131,11,31,78,188,184,241,227,200,147,43,95,206,188,185,243,231,208,163,75,159,78,189,186,245,235,216,179,107,223,206,189,187,247,239,224,195,139,255,31,79,190,188,249,243,232,211,171,95,207,190,189,251,247,240,227,203,159,79,191,190,253,251,248,243,235,223,207,191,191,255,255,0,6,40,224,128,4,22,104,224,129,8,38,168,224,130,12,54,232,224,131,16,70,40,225,132,20,86,104,225,133,24,102,168,225,134,28,118,232,225,135,32,134,40,226,136,36,150,104,226,137,40,166,168,226,138,44,182,232,226,139,48,198,40,227,140,52,214,104,227,141,56,230,168,227,142,60,246,232,227,143,64,6,41,228,144,68,22,105,228,145,72,38,169,228,146,76,54,233,228,147,80,70,41,229,148,84,86,105,229,149,88,102,169,229,150,92,118,233,229,151,96,134,41,230,152,100,150,105,230,153,104,166,169,230,154,108,182,233,230,155,112,198,41,231,156,116,214,105,231,157,120,230,169,231,158,124,246,233,231,159,128,6,42,232,160,132,22],
[106,232,161,136,38,170,232,162,140,54,234,232,163,144,70,42,233,164,148,86,106,233,165,152,102,170,233,166,156,118,234,233,167,160,134,42,234,168,164,150,106,234,169,168,166,170,234,170,172,182,234,234,171,176,198,27,42,235,172,180,214,106,235,173,184,230,170,235,174,188,246,234,235,175,192,6,43,236,176,196,90,20,16,0,44,0,0,0,0,64,1,64,1,129,0,255,0,0,0,0,0,0,0,0,0,0,8,255,0,1,8,28,72,176,160,193,131,8,19,42,92,200,176,161,195,135,16,35,74,156,72,177,162,197,139,24,51,106,220,200,177,163,199,143,32,67,138,28,73,178,164,201,147,40,83,170,92,201,178,165,203,151,48,99,202,156,73,179,166,205,155,56,115,234,220,201,179,167,207,159,64,131,10,29,74,180,168,209,163,72,147,42,93,202,180,169,211,167,80,163,74,157,74,181,170,213,171,88,179,106,221,202,181,171,215,175,96,195,138,29,75,182,172,217,179,104,211,170,93,203,182,173,219,183,112,227,202,157,75,183,174,221,187,120,243,234,221,203,183,175,223,191,128,3,11,30,76,184,176,225,195,136,19,43,94,204,184,177,227,199,144,35,75,158,76,185,178,229,203,152,51,107,222,204,185,179,231,207,160,67,139,30,77,186,180,233,211,168,83,171,94,205,186,181,235,215,176,99,203,158,77,187,182,237,219,184,115,235,222,205,187,183,239,223,192,131,11,31,78,188,184,241,227,200,147,43,95,206,188,185,243,231,208,163,75,159,78,189,186,245,235,216,179,107,223,206,189,187,247,239,224,195,139,255,31,79,190,188,249,243,232,211,171,95,207,190,189,251,247,240,227,203,159,79,191,190,253,251,248,243,235,223,207,191,191,255,255,0,6,40,224,128,4,22,104,224,129,8,38,168,224,130,12,54,232,224,131,16,70,40,225,132,20,86,104,225,133,24,102,168,225,134,28,118,232,225,135,32,134,40,226,136,36,150,104,226,137,40,166,168,226,138,44,182,232,226,139,48,198,40,227,140,52,214,104,227,141,56,230,168,227,142,60,246,232,227,143,64,6,41,228,144,68,22,105,228,145,72,38,169,228,146,76,54,233,228,147,80,70,41,229,148,84,86,105,229,149,88,102,169,229,150],
[92,118,233,229,151,96,134,41,230,152,100,150,105,230,153,104,166,169,230,154,108,182,233,230,155,112,198,41,231,156,116,214,105,231,157,120,230,169,231,158,124,246,233,231,159,128,6,42,232,160,132,22,106,232,161,136,38,170,232,162,140,54,234,232,163,144,70,42,233,164,148,86,106,233,165,152,102,170,233,166,156,118,234,233,167,160,134,42,234,168,164,150,106,234,169,168,166,170,234,170,172,182,234,234,171,176,198,27,42,235,172,180,214,106,235,173,184,230,170,235,174,188,246,234,235,175,192,6,43,236,176,196,90,20,16,0,44,0,0,0,0,64,1,64,1,129,0,0,255,0,0,0,0,0,0,0,0,0,8,255,0,1,8,28,72,176,160,193,131,8,19,42,92,200,176,161,195,135,16,35,74,156,72,177,162,197,139,24,51,106,220,200,177,163,199,143,32,67,138,28,73,178,164,201,147,40,83,170,92,201,178,165,203,151,48,99,202,156,73,179,166,205,155,56,115,234,220,201,179,167,207,159,64,131,10,29,74,180,168,209,163,72,147,42,93,202,180,169,211,167,80,163,74,157,74,181,170,213,171,88,179,106,221,202,181,171,215,175,96,195,138,29,75,182,172,217,179,104,211,170,93,203,182,173,219,183,112,227,202,157,75,183,174,221,187,120,243,234,221,203,183,175,223,191,128,3,11,30,76,184,176,225,195,136,19,43,94,204,184,177,227,199,144,35,75,158,76,185,178,229,203,152,51,107,222,204,185,179,231,207,160,67,139,30,77,186,180,233,211,168,83,171,94,205,186,181,235,215,176,99,203,158,77,187,182,237,219,184,115,235,222,205,187,183,239,223,192,131,11,31,78,188,184,241,227,200,147,43,95,206,188,185,243,231,208,163,75,159,78,189,186,245,235,216,179,107,223,206,189,187,247,239,224,195,139,255,31,79,190,188,249,243,232,211,171,95,207,190,189,251,247,240,227,203,159,79,191,190,253,251,248,243,235,223,207,191,191,255,255,0,6,40,224,128,4,22,104,224,129,8,38,168,224,130,12,54,232,224,131,16,70,40,225,132,20,86,104,225,133,24,102,168,225,134,28,118,232,225,135,32,134,40,226,136,36,150,104,226,137,40,166,168,226,138,44,182,232,226,139,48,198,40],
[227,140,52,214,104,227,141,56,230,168,227,142,60,246,232,227,143,64,6,41,228,144,68,22,105,228,145,72,38,169,228,146,76,54,233,228,147,80,70,41,229,148,84,86,105,229,149,88,102,169,229,150,92,118,233,229,151,96,134,41,230,152,100,150,105,230,153,104,166,169,230,154,108,182,233,230,155,112,198,41,231,156,116,214,105,231,157,120,230,169,231,158,124,246,233,231,159,128,6,42,232,160,132,22,106,232,161,136,38,170,232,162,140,54,234,232,163,144,70,42,233,164,148,86,106,233,165,152,102,170,233,166,156,118,234,233,167,160,134,42,234,168,164,150,106,234,169,168,166,170,234,170,172,182,234,234,171,176,198,27,42,235,172,180,214,106,235,173,184,230,170,235,174,188,246,234,235,175,192,6,43,236,176,196,90,20,16,0,59]]
}

BIN
tests/yellow.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB