// // OneBitDisplay (OLED/LCD/E-Paper library) // Copyright (c) 2020 BitBank Software, Inc. // Written by Larry Bank (bitbank@pobox.com) // Project started 3/23/2020 // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // http://www.apache.org/licenses/LICENSE-2.0 // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // // obd_gfx.inl - graphics functions // // forward declarations void InvertBytes(uint8_t *pData, uint8_t bLen); const uint8_t ucFont[]PROGMEM = { 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x06,0x5f,0x5f,0x06,0x00, 0x00,0x07,0x07,0x00,0x07,0x07,0x00,0x14,0x7f,0x7f,0x14,0x7f,0x7f,0x14, 0x24,0x2e,0x2a,0x6b,0x6b,0x3a,0x12,0x46,0x66,0x30,0x18,0x0c,0x66,0x62, 0x30,0x7a,0x4f,0x5d,0x37,0x7a,0x48,0x00,0x04,0x07,0x03,0x00,0x00,0x00, 0x00,0x1c,0x3e,0x63,0x41,0x00,0x00,0x00,0x41,0x63,0x3e,0x1c,0x00,0x00, 0x08,0x2a,0x3e,0x1c,0x3e,0x2a,0x08,0x00,0x08,0x08,0x3e,0x3e,0x08,0x08, 0x00,0x00,0x80,0xe0,0x60,0x00,0x00,0x00,0x08,0x08,0x08,0x08,0x08,0x08, 0x00,0x00,0x00,0x60,0x60,0x00,0x00,0x60,0x30,0x18,0x0c,0x06,0x03,0x01, 0x3e,0x7f,0x59,0x4d,0x47,0x7f,0x3e,0x40,0x42,0x7f,0x7f,0x40,0x40,0x00, 0x62,0x73,0x59,0x49,0x6f,0x66,0x00,0x22,0x63,0x49,0x49,0x7f,0x36,0x00, 0x18,0x1c,0x16,0x53,0x7f,0x7f,0x50,0x27,0x67,0x45,0x45,0x7d,0x39,0x00, 0x3c,0x7e,0x4b,0x49,0x79,0x30,0x00,0x03,0x03,0x71,0x79,0x0f,0x07,0x00, 0x36,0x7f,0x49,0x49,0x7f,0x36,0x00,0x06,0x4f,0x49,0x69,0x3f,0x1e,0x00, 0x00,0x00,0x00,0x66,0x66,0x00,0x00,0x00,0x00,0x80,0xe6,0x66,0x00,0x00, 0x08,0x1c,0x36,0x63,0x41,0x00,0x00,0x00,0x14,0x14,0x14,0x14,0x14,0x14, 0x00,0x41,0x63,0x36,0x1c,0x08,0x00,0x00,0x02,0x03,0x59,0x5d,0x07,0x02, 0x3e,0x7f,0x41,0x5d,0x5d,0x5f,0x0e,0x7c,0x7e,0x13,0x13,0x7e,0x7c,0x00, 0x41,0x7f,0x7f,0x49,0x49,0x7f,0x36,0x1c,0x3e,0x63,0x41,0x41,0x63,0x22, 0x41,0x7f,0x7f,0x41,0x63,0x3e,0x1c,0x41,0x7f,0x7f,0x49,0x5d,0x41,0x63, 0x41,0x7f,0x7f,0x49,0x1d,0x01,0x03,0x1c,0x3e,0x63,0x41,0x51,0x33,0x72, 0x7f,0x7f,0x08,0x08,0x7f,0x7f,0x00,0x00,0x41,0x7f,0x7f,0x41,0x00,0x00, 0x30,0x70,0x40,0x41,0x7f,0x3f,0x01,0x41,0x7f,0x7f,0x08,0x1c,0x77,0x63, 0x41,0x7f,0x7f,0x41,0x40,0x60,0x70,0x7f,0x7f,0x0e,0x1c,0x0e,0x7f,0x7f, 0x7f,0x7f,0x06,0x0c,0x18,0x7f,0x7f,0x1c,0x3e,0x63,0x41,0x63,0x3e,0x1c, 0x41,0x7f,0x7f,0x49,0x09,0x0f,0x06,0x1e,0x3f,0x21,0x31,0x61,0x7f,0x5e, 0x41,0x7f,0x7f,0x09,0x19,0x7f,0x66,0x26,0x6f,0x4d,0x49,0x59,0x73,0x32, 0x03,0x41,0x7f,0x7f,0x41,0x03,0x00,0x7f,0x7f,0x40,0x40,0x7f,0x7f,0x00, 0x1f,0x3f,0x60,0x60,0x3f,0x1f,0x00,0x3f,0x7f,0x60,0x30,0x60,0x7f,0x3f, 0x63,0x77,0x1c,0x08,0x1c,0x77,0x63,0x07,0x4f,0x78,0x78,0x4f,0x07,0x00, 0x47,0x63,0x71,0x59,0x4d,0x67,0x73,0x00,0x7f,0x7f,0x41,0x41,0x00,0x00, 0x01,0x03,0x06,0x0c,0x18,0x30,0x60,0x00,0x41,0x41,0x7f,0x7f,0x00,0x00, 0x08,0x0c,0x06,0x03,0x06,0x0c,0x08,0x80,0x80,0x80,0x80,0x80,0x80,0x80, 0x00,0x00,0x03,0x07,0x04,0x00,0x00,0x20,0x74,0x54,0x54,0x3c,0x78,0x40, 0x41,0x7f,0x3f,0x48,0x48,0x78,0x30,0x38,0x7c,0x44,0x44,0x6c,0x28,0x00, 0x30,0x78,0x48,0x49,0x3f,0x7f,0x40,0x38,0x7c,0x54,0x54,0x5c,0x18,0x00, 0x48,0x7e,0x7f,0x49,0x03,0x06,0x00,0x98,0xbc,0xa4,0xa4,0xf8,0x7c,0x04, 0x41,0x7f,0x7f,0x08,0x04,0x7c,0x78,0x00,0x44,0x7d,0x7d,0x40,0x00,0x00, 0x60,0xe0,0x80,0x84,0xfd,0x7d,0x00,0x41,0x7f,0x7f,0x10,0x38,0x6c,0x44, 0x00,0x41,0x7f,0x7f,0x40,0x00,0x00,0x7c,0x7c,0x18,0x78,0x1c,0x7c,0x78, 0x7c,0x78,0x04,0x04,0x7c,0x78,0x00,0x38,0x7c,0x44,0x44,0x7c,0x38,0x00, 0x84,0xfc,0xf8,0xa4,0x24,0x3c,0x18,0x18,0x3c,0x24,0xa4,0xf8,0xfc,0x84, 0x44,0x7c,0x78,0x4c,0x04,0x0c,0x18,0x48,0x5c,0x54,0x74,0x64,0x24,0x00, 0x04,0x04,0x3e,0x7f,0x44,0x24,0x00,0x3c,0x7c,0x40,0x40,0x3c,0x7c,0x40, 0x1c,0x3c,0x60,0x60,0x3c,0x1c,0x00,0x3c,0x7c,0x60,0x30,0x60,0x7c,0x3c, 0x44,0x6c,0x38,0x10,0x38,0x6c,0x44,0x9c,0xbc,0xa0,0xa0,0xfc,0x7c,0x00, 0x4c,0x64,0x74,0x5c,0x4c,0x64,0x00,0x08,0x08,0x3e,0x77,0x41,0x41,0x00, 0x00,0x00,0x00,0x77,0x77,0x00,0x00,0x41,0x41,0x77,0x3e,0x08,0x08,0x00, 0x02,0x03,0x01,0x03,0x02,0x03,0x01,0x70,0x78,0x4c,0x46,0x4c,0x78,0x70}; // AVR MCUs have very little memory; save 6K of FLASH by stretching the 'normal' // font instead of using this large font #ifndef WIMPY_MCU const uint8_t ucBigFont[]PROGMEM = { 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0xfc,0xfc,0xff,0xff,0xff,0xff,0xfc,0xfc,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x3f,0x3f,0x3f,0x3f,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x0f,0x0f,0x0f,0x0f,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0xc0,0xc0,0xc0,0xc0,0x00,0x00,0x00,0x00,0xc0,0xc0,0xc0,0xc0,0x00,0x00, 0x00,0x00,0x0f,0x0f,0x3f,0x3f,0x00,0x00,0x00,0x00,0x3f,0x3f,0x0f,0x0f,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0xc0,0xc0,0xfc,0xfc,0xfc,0xfc,0xc0,0xc0,0xfc,0xfc,0xfc,0xfc,0xc0,0xc0,0x00,0x00, 0xc0,0xc0,0xff,0xff,0xff,0xff,0xc0,0xc0,0xff,0xff,0xff,0xff,0xc0,0xc0,0x00,0x00, 0x00,0x00,0x0f,0x0f,0x0f,0x0f,0x00,0x00,0x0f,0x0f,0x0f,0x0f,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xf0,0xf0,0xf0,0x00,0x00,0x00,0x00,0x00,0x00, 0xfc,0xfc,0xff,0xff,0x03,0x03,0x03,0x03,0x03,0x03,0x0f,0x0f,0x3c,0x3c,0x00,0x00, 0xf0,0xf0,0xc3,0xc3,0x03,0x03,0x03,0x03,0x03,0x03,0xff,0xff,0xfc,0xfc,0x00,0x00, 0x00,0x00,0x03,0x03,0x03,0x03,0x3f,0x3f,0x3f,0x3f,0x03,0x03,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0xf0,0xf0,0xf0,0xf0,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xc0,0xf0,0xf0,0x00,0x00, 0x00,0x00,0xc0,0xc0,0xf0,0xf0,0x3c,0x3c,0x0f,0x0f,0x03,0x03,0x00,0x00,0x00,0x00, 0x0f,0x0f,0x03,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x0f,0x0f,0x0f,0x0f,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x3c,0x3c,0xff,0xff,0xc3,0xc3,0xff,0xff,0x3c,0x3c,0x00,0x00,0x00,0x00, 0xfc,0xfc,0xff,0xff,0x03,0x03,0x0f,0x0f,0xfc,0xfc,0xff,0xff,0x03,0x03,0x00,0x00, 0x03,0x03,0x0f,0x0f,0x0c,0x0c,0x0c,0x0c,0x03,0x03,0x0f,0x0f,0x0c,0x0c,0x00,0x00, 0x00,0x00,0x00,0x00,0xc0,0xc0,0xc0,0xc0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x30,0x30,0x3f,0x3f,0x0f,0x0f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0xf0,0xf0,0xfc,0xfc,0x0f,0x0f,0x03,0x03,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x03,0x0f,0x0f,0x0c,0x0c,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x03,0x03,0x0f,0x0f,0xfc,0xfc,0xf0,0xf0,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x0c,0x0c,0x0f,0x0f,0x03,0x03,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0xc0,0xc0,0xc0,0xc0,0x00,0x00,0x00,0x00,0xc0,0xc0,0xc0,0xc0,0x00,0x00, 0x0c,0x0c,0xcc,0xcc,0xff,0xff,0x3f,0x3f,0x3f,0x3f,0xff,0xff,0xcc,0xcc,0x0c,0x0c, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xc0,0xc0,0xc0,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x0c,0x0c,0x0c,0x0c,0xff,0xff,0xff,0xff,0x0c,0x0c,0x0c,0x0c,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xc0,0xc0,0xc0,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x30,0x30,0x3f,0x3f,0x0f,0x0f,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x0f,0x0f,0x0f,0x0f,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xc0,0xf0,0xf0,0x00,0x00, 0x00,0x00,0xc0,0xc0,0xf0,0xf0,0x3c,0x3c,0x0f,0x0f,0x03,0x03,0x00,0x00,0x00,0x00, 0x0f,0x0f,0x03,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0xfc,0xfc,0xff,0xff,0x03,0x03,0x03,0x03,0xc3,0xc3,0xff,0xff,0xfc,0xfc,0x00,0x00, 0xff,0xff,0xff,0xff,0x30,0x30,0x0f,0x0f,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00, 0x03,0x03,0x0f,0x0f,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0f,0x0f,0x03,0x03,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x30,0x30,0x3c,0x3c,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x0c,0x0c,0x0c,0x0c,0x0f,0x0f,0x0f,0x0f,0x0c,0x0c,0x0c,0x0c,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x0c,0x0c,0x0f,0x0f,0x03,0x03,0x03,0x03,0xc3,0xc3,0xff,0xff,0x3c,0x3c,0x00,0x00, 0xc0,0xc0,0xf0,0xf0,0x3c,0x3c,0x0f,0x0f,0x03,0x03,0x00,0x00,0x00,0x00,0x00,0x00, 0x0f,0x0f,0x0f,0x0f,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0f,0x0f,0x0f,0x0f,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x0c,0x0c,0x0f,0x0f,0x03,0x03,0x03,0x03,0x03,0x03,0xff,0xff,0xfc,0xfc,0x00,0x00, 0x00,0x00,0x00,0x00,0x03,0x03,0x03,0x03,0x03,0x03,0xff,0xff,0xfc,0xfc,0x00,0x00, 0x03,0x03,0x0f,0x0f,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0f,0x0f,0x03,0x03,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0xc0,0xc0,0xf0,0xf0,0x3c,0x3c,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00, 0x0f,0x0f,0x0f,0x0f,0x0c,0x0c,0x0c,0x0c,0xff,0xff,0xff,0xff,0x0c,0x0c,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x0c,0x0c,0x0f,0x0f,0x0f,0x0f,0x0c,0x0c,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0xff,0xff,0xff,0xff,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x00,0x00, 0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x0f,0x0f,0xff,0xff,0xfc,0xfc,0x00,0x00, 0x03,0x03,0x0f,0x0f,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0f,0x0f,0x03,0x03,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0xf0,0xf0,0xfc,0xfc,0x0f,0x0f,0x03,0x03,0x03,0x03,0x00,0x00,0x00,0x00,0x00,0x00, 0xff,0xff,0xff,0xff,0x03,0x03,0x03,0x03,0x03,0x03,0xff,0xff,0xfc,0xfc,0x00,0x00, 0x03,0x03,0x0f,0x0f,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0f,0x0f,0x03,0x03,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x0f,0x0f,0x0f,0x0f,0x03,0x03,0x03,0x03,0x03,0x03,0xff,0xff,0xff,0xff,0x00,0x00, 0x00,0x00,0x00,0x00,0xf0,0xf0,0xfc,0xfc,0x0f,0x0f,0x03,0x03,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x0f,0x0f,0x0f,0x0f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0xfc,0xfc,0xff,0xff,0x03,0x03,0x03,0x03,0x03,0x03,0xff,0xff,0xfc,0xfc,0x00,0x00, 0xfc,0xfc,0xff,0xff,0x03,0x03,0x03,0x03,0x03,0x03,0xff,0xff,0xfc,0xfc,0x00,0x00, 0x03,0x03,0x0f,0x0f,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0f,0x0f,0x03,0x03,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0xfc,0xfc,0xff,0xff,0x03,0x03,0x03,0x03,0x03,0x03,0xff,0xff,0xfc,0xfc,0x00,0x00, 0x00,0x00,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0xff,0xff,0xff,0xff,0x00,0x00, 0x00,0x00,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0f,0x0f,0x03,0x03,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xf0,0xf0,0xf0,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xc0,0xc0,0xc0,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x03,0x03,0x03,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xf0,0xf0,0xf0,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xc0,0xc0,0xc0,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x0c,0x0c,0x0f,0x0f,0x03,0x03,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xc0,0xf0,0xf0,0x3c,0x3c,0x0c,0x0c,0x00,0x00, 0x00,0x00,0x0c,0x0c,0x3f,0x3f,0xf3,0xf3,0xc0,0xc0,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x03,0x0f,0x0f,0x0c,0x0c,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0xc3,0xc3,0xc3,0xc3,0xc3,0xc3,0xc3,0xc3,0xc3,0xc3,0xc3,0xc3,0xc3,0xc3,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x0c,0x0c,0x3c,0x3c,0xf0,0xf0,0xc0,0xc0,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xc0,0xf3,0xf3,0x3f,0x3f,0x0c,0x0c,0x00,0x00, 0x00,0x00,0x0c,0x0c,0x0f,0x0f,0x03,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x3c,0x3c,0x3f,0x3f,0x03,0x03,0x03,0x03,0xc3,0xc3,0xff,0xff,0x3c,0x3c,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x3f,0x3f,0x3f,0x3f,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x0f,0x0f,0x0f,0x0f,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0xf0,0xf0,0xfc,0xfc,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0xfc,0xfc,0xf0,0xf0,0x00,0x00, 0xff,0xff,0xff,0xff,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0x3f,0x3f,0x00,0x00, 0x03,0x03,0x0f,0x0f,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0xc0,0xc0,0xf0,0xf0,0x3c,0x3c,0x0f,0x0f,0x3c,0x3c,0xf0,0xf0,0xc0,0xc0,0x00,0x00, 0xff,0xff,0xff,0xff,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0xff,0xff,0xff,0xff,0x00,0x00, 0x0f,0x0f,0x0f,0x0f,0x00,0x00,0x00,0x00,0x00,0x00,0x0f,0x0f,0x0f,0x0f,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x03,0x03,0xff,0xff,0xff,0xff,0x03,0x03,0x03,0x03,0xff,0xff,0xfc,0xfc,0x00,0x00, 0x00,0x00,0xff,0xff,0xff,0xff,0x03,0x03,0x03,0x03,0xff,0xff,0xfc,0xfc,0x00,0x00, 0x0c,0x0c,0x0f,0x0f,0x0f,0x0f,0x0c,0x0c,0x0c,0x0c,0x0f,0x0f,0x03,0x03,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0xf0,0xf0,0xfc,0xfc,0x0f,0x0f,0x03,0x03,0x03,0x03,0x0f,0x0f,0x3c,0x3c,0x00,0x00, 0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xc0,0x00,0x00, 0x00,0x00,0x03,0x03,0x0f,0x0f,0x0c,0x0c,0x0c,0x0c,0x0f,0x0f,0x03,0x03,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x03,0x03,0xff,0xff,0xff,0xff,0x03,0x03,0x0f,0x0f,0xfc,0xfc,0xf0,0xf0,0x00,0x00, 0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00, 0x0c,0x0c,0x0f,0x0f,0x0f,0x0f,0x0c,0x0c,0x0f,0x0f,0x03,0x03,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x03,0x03,0xff,0xff,0xff,0xff,0x03,0x03,0xc3,0xc3,0x0f,0x0f,0x3f,0x3f,0x00,0x00, 0x00,0x00,0xff,0xff,0xff,0xff,0x03,0x03,0x0f,0x0f,0x00,0x00,0xc0,0xc0,0x00,0x00, 0x0c,0x0c,0x0f,0x0f,0x0f,0x0f,0x0c,0x0c,0x0c,0x0c,0x0f,0x0f,0x0f,0x0f,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x03,0x03,0xff,0xff,0xff,0xff,0x03,0x03,0xc3,0xc3,0x0f,0x0f,0x3f,0x3f,0x00,0x00, 0x00,0x00,0xff,0xff,0xff,0xff,0x03,0x03,0x0f,0x0f,0x00,0x00,0x00,0x00,0x00,0x00, 0x0c,0x0c,0x0f,0x0f,0x0f,0x0f,0x0c,0x0c,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0xf0,0xf0,0xfc,0xfc,0x0f,0x0f,0x03,0x03,0x03,0x03,0x0f,0x0f,0x3c,0x3c,0x00,0x00, 0xff,0xff,0xff,0xff,0x00,0x00,0x0c,0x0c,0x0c,0x0c,0xfc,0xfc,0xfc,0xfc,0x00,0x00, 0x00,0x00,0x03,0x03,0x0f,0x0f,0x0c,0x0c,0x0c,0x0c,0x03,0x03,0x0f,0x0f,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00, 0xff,0xff,0xff,0xff,0x03,0x03,0x03,0x03,0x03,0x03,0xff,0xff,0xff,0xff,0x00,0x00, 0x0f,0x0f,0x0f,0x0f,0x00,0x00,0x00,0x00,0x00,0x00,0x0f,0x0f,0x0f,0x0f,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x03,0x03,0xff,0xff,0xff,0xff,0x03,0x03,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x0c,0x0c,0x0f,0x0f,0x0f,0x0f,0x0c,0x0c,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x03,0xff,0xff,0xff,0xff,0x03,0x03,0x00,0x00, 0xf0,0xf0,0xf0,0xf0,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00, 0x03,0x03,0x0f,0x0f,0x0c,0x0c,0x0c,0x0c,0x0f,0x0f,0x03,0x03,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x03,0x03,0xff,0xff,0xff,0xff,0x00,0x00,0xf0,0xf0,0xff,0xff,0x0f,0x0f,0x00,0x00, 0x00,0x00,0xff,0xff,0xff,0xff,0x0f,0x0f,0x3f,0x3f,0xf0,0xf0,0xc0,0xc0,0x00,0x00, 0x0c,0x0c,0x0f,0x0f,0x0f,0x0f,0x00,0x00,0x00,0x00,0x0f,0x0f,0x0f,0x0f,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x03,0x03,0xff,0xff,0xff,0xff,0x03,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xc0,0x00,0x00, 0x0c,0x0c,0x0f,0x0f,0x0f,0x0f,0x0c,0x0c,0x0c,0x0c,0x0f,0x0f,0x0f,0x0f,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0xff,0xff,0xff,0xff,0xfc,0xfc,0xf0,0xf0,0xfc,0xfc,0xff,0xff,0xff,0xff,0x00,0x00, 0xff,0xff,0xff,0xff,0x00,0x00,0x03,0x03,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00, 0x0f,0x0f,0x0f,0x0f,0x00,0x00,0x00,0x00,0x00,0x00,0x0f,0x0f,0x0f,0x0f,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0xff,0xff,0xff,0xff,0xfc,0xfc,0xf0,0xf0,0xc0,0xc0,0xff,0xff,0xff,0xff,0x00,0x00, 0xff,0xff,0xff,0xff,0x00,0x00,0x03,0x03,0x0f,0x0f,0xff,0xff,0xff,0xff,0x00,0x00, 0x0f,0x0f,0x0f,0x0f,0x00,0x00,0x00,0x00,0x00,0x00,0x0f,0x0f,0x0f,0x0f,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0xf0,0xf0,0xfc,0xfc,0x0f,0x0f,0x03,0x03,0x0f,0x0f,0xfc,0xfc,0xf0,0xf0,0x00,0x00, 0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00, 0x00,0x00,0x03,0x03,0x0f,0x0f,0x0c,0x0c,0x0f,0x0f,0x03,0x03,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x03,0x03,0xff,0xff,0xff,0xff,0x03,0x03,0x03,0x03,0xff,0xff,0xfc,0xfc,0x00,0x00, 0x00,0x00,0xff,0xff,0xff,0xff,0x03,0x03,0x03,0x03,0x03,0x03,0x00,0x00,0x00,0x00, 0x0c,0x0c,0x0f,0x0f,0x0f,0x0f,0x0c,0x0c,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0xfc,0xfc,0xff,0xff,0x03,0x03,0x03,0x03,0x03,0x03,0xff,0xff,0xfc,0xfc,0x00,0x00, 0xff,0xff,0xff,0xff,0x00,0x00,0xc0,0xc0,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00, 0x03,0x03,0x0f,0x0f,0x0c,0x0c,0x0f,0x0f,0xff,0xff,0xff,0xff,0xc3,0xc3,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x03,0x03,0xff,0xff,0xff,0xff,0x03,0x03,0x03,0x03,0xff,0xff,0xfc,0xfc,0x00,0x00, 0x00,0x00,0xff,0xff,0xff,0xff,0x03,0x03,0x0f,0x0f,0xff,0xff,0xf0,0xf0,0x00,0x00, 0x0c,0x0c,0x0f,0x0f,0x0f,0x0f,0x00,0x00,0x00,0x00,0x0f,0x0f,0x0f,0x0f,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x3c,0x3c,0xff,0xff,0xc3,0xc3,0x03,0x03,0x03,0x03,0x3f,0x3f,0x3c,0x3c,0x00,0x00, 0xc0,0xc0,0xc0,0xc0,0x03,0x03,0x03,0x03,0x0f,0x0f,0xfc,0xfc,0xf0,0xf0,0x00,0x00, 0x03,0x03,0x0f,0x0f,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0f,0x0f,0x03,0x03,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x3f,0x3f,0x0f,0x0f,0xff,0xff,0xff,0xff,0x0f,0x0f,0x3f,0x3f,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x0c,0x0c,0x0f,0x0f,0x0f,0x0f,0x0c,0x0c,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00, 0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00, 0x03,0x03,0x0f,0x0f,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0f,0x0f,0x03,0x03,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00, 0x3f,0x3f,0xff,0xff,0xc0,0xc0,0x00,0x00,0xc0,0xc0,0xff,0xff,0x3f,0x3f,0x00,0x00, 0x00,0x00,0x00,0x00,0x03,0x03,0x0f,0x0f,0x03,0x03,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00, 0xff,0xff,0xff,0xff,0xc0,0xc0,0xfc,0xfc,0xc0,0xc0,0xff,0xff,0xff,0xff,0x00,0x00, 0x00,0x00,0x0f,0x0f,0x0f,0x0f,0x00,0x00,0x0f,0x0f,0x0f,0x0f,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x0f,0x0f,0xff,0xff,0xf0,0xf0,0x00,0x00,0xf0,0xf0,0xff,0xff,0x0f,0x0f,0x00,0x00, 0x00,0x00,0xf0,0xf0,0xff,0xff,0x0f,0x0f,0xff,0xff,0xf0,0xf0,0x00,0x00,0x00,0x00, 0x0f,0x0f,0x0f,0x0f,0x00,0x00,0x00,0x00,0x00,0x00,0x0f,0x0f,0x0f,0x0f,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00, 0x00,0x00,0x00,0x00,0x03,0x03,0xff,0xff,0xff,0xff,0x03,0x03,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x0c,0x0c,0x0f,0x0f,0x0f,0x0f,0x0c,0x0c,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x3f,0x3f,0x0f,0x0f,0x03,0x03,0x03,0x03,0xc3,0xc3,0xff,0xff,0x3f,0x3f,0x00,0x00, 0xc0,0xc0,0xf0,0xf0,0x3c,0x3c,0x0f,0x0f,0x03,0x03,0x00,0x00,0xc0,0xc0,0x00,0x00, 0x0f,0x0f,0x0f,0x0f,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0f,0x0f,0x0f,0x0f,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0x03,0x03,0x03,0x03,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x0f,0x0f,0x0f,0x0f,0x0c,0x0c,0x0c,0x0c,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0xfc,0xfc,0xf0,0xf0,0xc0,0xc0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x03,0x03,0x0f,0x0f,0x3f,0x3f,0xfc,0xfc,0xf0,0xf0,0xc0,0xc0,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x03,0x0f,0x0f,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x03,0x03,0x03,0x03,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x0c,0x0c,0x0c,0x0c,0x0f,0x0f,0x0f,0x0f,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0xc0,0xc0,0xf0,0xf0,0xc0,0xc0,0x00,0x00,0x00,0x00,0x00,0x00, 0x0c,0x0c,0x0f,0x0f,0x03,0x03,0x00,0x00,0x03,0x03,0x0f,0x0f,0x0c,0x0c,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0, 0x00,0x00,0x00,0x00,0xf0,0xf0,0xf0,0xf0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x03,0x03,0x03,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0x00,0x00,0x00,0x00,0x00,0x00, 0xf0,0xf0,0xfc,0xfc,0x0c,0x0c,0x0c,0x0c,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00, 0x03,0x03,0x0f,0x0f,0x0c,0x0c,0x0c,0x0c,0x03,0x03,0x0f,0x0f,0x0c,0x0c,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x03,0x03,0xff,0xff,0xff,0xff,0xc0,0xc0,0xc0,0xc0,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00,0x03,0x03,0xff,0xff,0xfc,0xfc,0x00,0x00, 0x0c,0x0c,0x0f,0x0f,0x03,0x03,0x0c,0x0c,0x0c,0x0c,0x0f,0x0f,0x03,0x03,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0x00,0x00,0x00,0x00, 0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x03,0x03,0x03,0x00,0x00, 0x03,0x03,0x0f,0x0f,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0f,0x0f,0x03,0x03,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0xc0,0xc0,0xc3,0xc3,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00, 0xfc,0xfc,0xff,0xff,0x03,0x03,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00, 0x03,0x03,0x0f,0x0f,0x0c,0x0c,0x0c,0x0c,0x03,0x03,0x0f,0x0f,0x0c,0x0c,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0x00,0x00,0x00,0x00, 0xff,0xff,0xff,0xff,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0f,0x0f,0x0f,0x0f,0x00,0x00, 0x03,0x03,0x0f,0x0f,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0f,0x0f,0x03,0x03,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0xfc,0xfc,0xff,0xff,0x03,0x03,0x0f,0x0f,0x3c,0x3c,0x00,0x00,0x00,0x00, 0x03,0x03,0xff,0xff,0xff,0xff,0x03,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x0c,0x0c,0x0f,0x0f,0x0f,0x0f,0x0c,0x0c,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0x00,0x00,0xc0,0xc0,0xc0,0xc0,0x00,0x00, 0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00, 0xc3,0xc3,0xcf,0xcf,0x0c,0x0c,0x0c,0x0c,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00, 0x00,0x00,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x00,0x00,0x00,0x00,0x00,0x00, 0x03,0x03,0xff,0xff,0xff,0xff,0x00,0x00,0xc0,0xc0,0xc0,0xc0,0x00,0x00,0x00,0x00, 0x00,0x00,0xff,0xff,0xff,0xff,0x03,0x03,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00, 0x0c,0x0c,0x0f,0x0f,0x0f,0x0f,0x00,0x00,0x00,0x00,0x0f,0x0f,0x0f,0x0f,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0xc0,0xc0,0xcf,0xcf,0xcf,0xcf,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x0c,0x0c,0x0f,0x0f,0x0f,0x0f,0x0c,0x0c,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xc0,0xcf,0xcf,0xcf,0xcf,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00, 0x00,0x00,0xf0,0xf0,0xf0,0xf0,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00, 0x00,0x00,0x00,0x00,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x00,0x00,0x00,0x00, 0x03,0x03,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0xc0,0xc0,0xc0,0xc0,0x00,0x00, 0x00,0x00,0xff,0xff,0xff,0xff,0x3c,0x3c,0xff,0xff,0xc3,0xc3,0x00,0x00,0x00,0x00, 0x0c,0x0c,0x0f,0x0f,0x0f,0x0f,0x00,0x00,0x00,0x00,0x0f,0x0f,0x0f,0x0f,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x03,0x03,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x0c,0x0c,0x0f,0x0f,0x0f,0x0f,0x0c,0x0c,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0x00,0x00,0xc0,0xc0,0xc0,0xc0,0x00,0x00,0x00,0x00, 0xff,0xff,0xff,0xff,0x03,0x03,0xff,0xff,0x03,0x03,0xff,0xff,0xff,0xff,0x00,0x00, 0x0f,0x0f,0x0f,0x0f,0x00,0x00,0x0f,0x0f,0x00,0x00,0x0f,0x0f,0x0f,0x0f,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0xc0,0xc0,0xc0,0xc0,0x00,0x00,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0x00,0x00,0x00,0x00, 0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00, 0x00,0x00,0x0f,0x0f,0x0f,0x0f,0x00,0x00,0x00,0x00,0x0f,0x0f,0x0f,0x0f,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0x00,0x00,0x00,0x00, 0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00, 0x03,0x03,0x0f,0x0f,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0f,0x0f,0x03,0x03,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0xc0,0xc0,0xc0,0xc0,0x00,0x00,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0x00,0x00,0x00,0x00, 0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00, 0x00,0x00,0xff,0xff,0xff,0xff,0x0c,0x0c,0x0c,0x0c,0x0f,0x0f,0x03,0x03,0x00,0x00, 0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0x00,0x00,0xc0,0xc0,0xc0,0xc0,0x00,0x00, 0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00, 0x03,0x03,0x0f,0x0f,0x0c,0x0c,0x0c,0x0c,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x00,0x00, 0xc0,0xc0,0xc0,0xc0,0x00,0x00,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0x00,0x00,0x00,0x00, 0x00,0x00,0xff,0xff,0xff,0xff,0x03,0x03,0x00,0x00,0x03,0x03,0x0f,0x0f,0x00,0x00, 0x0c,0x0c,0x0f,0x0f,0x0f,0x0f,0x0c,0x0c,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0x00,0x00,0x00,0x00, 0x03,0x03,0x0f,0x0f,0x3c,0x3c,0x30,0x30,0xf0,0xf0,0xc3,0xc3,0x03,0x03,0x00,0x00, 0x03,0x03,0x0f,0x0f,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0f,0x0f,0x03,0x03,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0xc0,0xc0,0xc0,0xc0,0xfc,0xfc,0xff,0xff,0xc0,0xc0,0xc0,0xc0,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x03,0x03,0x0f,0x0f,0x0c,0x0c,0x0f,0x0f,0x03,0x03,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0xc0,0xc0,0xc0,0xc0,0x00,0x00,0x00,0x00,0xc0,0xc0,0xc0,0xc0,0x00,0x00,0x00,0x00, 0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00, 0x03,0x03,0x0f,0x0f,0x0c,0x0c,0x0c,0x0c,0x03,0x03,0x0f,0x0f,0x0c,0x0c,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0xc0,0xc0,0xc0,0xc0,0x00,0x00,0x00,0x00,0xc0,0xc0,0xc0,0xc0,0x00,0x00, 0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00, 0x00,0x00,0x00,0x00,0x03,0x03,0x0f,0x0f,0x0f,0x0f,0x03,0x03,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0xc0,0xc0,0xc0,0xc0,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xc0,0xc0,0xc0,0x00,0x00, 0xff,0xff,0xff,0xff,0x00,0x00,0xf0,0xf0,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00, 0x03,0x03,0x0f,0x0f,0x0f,0x0f,0x03,0x03,0x0f,0x0f,0x0f,0x0f,0x03,0x03,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0xc0,0xc0,0xc0,0xc0,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xc0,0xc0,0xc0,0x00,0x00, 0x00,0x00,0x03,0x03,0xff,0xff,0xfc,0xfc,0xff,0xff,0x03,0x03,0x00,0x00,0x00,0x00, 0x0c,0x0c,0x0f,0x0f,0x03,0x03,0x00,0x00,0x03,0x03,0x0f,0x0f,0x0c,0x0c,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0xc0,0xc0,0xc0,0xc0,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xc0,0xc0,0xc0,0x00,0x00, 0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00, 0x03,0x03,0x0f,0x0f,0x0c,0x0c,0x0c,0x0c,0xcc,0xcc,0xff,0xff,0x3f,0x3f,0x00,0x00, 0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x00,0x00,0x00,0x00,0x00,0x00, 0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0x00,0x00, 0x03,0x03,0xc3,0xc3,0xf0,0xf0,0x3c,0x3c,0x0f,0x0f,0x03,0x03,0x00,0x00,0x00,0x00, 0x0f,0x0f,0x0f,0x0f,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0f,0x0f,0x0f,0x0f,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0xfc,0xfc,0xff,0xff,0x03,0x03,0x03,0x03,0x00,0x00, 0x00,0x00,0x03,0x03,0x03,0x03,0xff,0xff,0xfc,0xfc,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x03,0x0f,0x0f,0x0c,0x0c,0x0c,0x0c,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0xfc,0xfc,0xfc,0xfc,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x0f,0x0f,0x0f,0x0f,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x03,0x03,0x03,0x03,0xff,0xff,0xfc,0xfc,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0xfc,0xfc,0xff,0xff,0x03,0x03,0x03,0x03,0x00,0x00, 0x00,0x00,0x0c,0x0c,0x0c,0x0c,0x0f,0x0f,0x03,0x03,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x0c,0x0c,0x0f,0x0f,0x03,0x03,0x0f,0x0f,0x0c,0x0c,0x0f,0x0f,0x03,0x03,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0xc0,0xc0,0xf0,0xf0,0xc0,0xc0,0x00,0x00,0x00,0x00,0x00,0x00, 0xfc,0xfc,0xff,0xff,0x03,0x03,0x00,0x00,0x03,0x03,0xff,0xff,0xfc,0xfc,0x00,0x00, 0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}; #endif // !WIMPY_MCU // 5x7 font (in 6x8 cell) const uint8_t ucSmallFont[] PROGMEM = { 0x00,0x00,0x00,0x00,0x00, 0x00,0x06,0x5f,0x06,0x00, 0x07,0x03,0x00,0x07,0x03, 0x24,0x7e,0x24,0x7e,0x24, 0x24,0x2b,0x6a,0x12,0x00, 0x63,0x13,0x08,0x64,0x63, 0x36,0x49,0x56,0x20,0x50, 0x00,0x07,0x03,0x00,0x00, 0x00,0x3e,0x41,0x00,0x00, 0x00,0x41,0x3e,0x00,0x00, 0x08,0x3e,0x1c,0x3e,0x08, 0x08,0x08,0x3e,0x08,0x08, 0x00,0xe0,0x60,0x00,0x00, 0x08,0x08,0x08,0x08,0x08, 0x00,0x60,0x60,0x00,0x00, 0x20,0x10,0x08,0x04,0x02, 0x3e,0x51,0x49,0x45,0x3e, 0x00,0x42,0x7f,0x40,0x00, 0x62,0x51,0x49,0x49,0x46, 0x22,0x49,0x49,0x49,0x36, 0x18,0x14,0x12,0x7f,0x10, 0x2f,0x49,0x49,0x49,0x31, 0x3c,0x4a,0x49,0x49,0x30, 0x01,0x71,0x09,0x05,0x03, 0x36,0x49,0x49,0x49,0x36, 0x06,0x49,0x49,0x29,0x1e, 0x00,0x6c,0x6c,0x00,0x00, 0x00,0xec,0x6c,0x00,0x00, 0x08,0x14,0x22,0x41,0x00, 0x24,0x24,0x24,0x24,0x24, 0x00,0x41,0x22,0x14,0x08, 0x02,0x01,0x59,0x09,0x06, 0x3e,0x41,0x5d,0x55,0x1e, 0x7e,0x11,0x11,0x11,0x7e, 0x7f,0x49,0x49,0x49,0x36, 0x3e,0x41,0x41,0x41,0x22, 0x7f,0x41,0x41,0x41,0x3e, 0x7f,0x49,0x49,0x49,0x41, 0x7f,0x09,0x09,0x09,0x01, 0x3e,0x41,0x49,0x49,0x7a, 0x7f,0x08,0x08,0x08,0x7f, 0x00,0x41,0x7f,0x41,0x00, 0x30,0x40,0x40,0x40,0x3f, 0x7f,0x08,0x14,0x22,0x41, 0x7f,0x40,0x40,0x40,0x40, 0x7f,0x02,0x04,0x02,0x7f, 0x7f,0x02,0x04,0x08,0x7f, 0x3e,0x41,0x41,0x41,0x3e, 0x7f,0x09,0x09,0x09,0x06, 0x3e,0x41,0x51,0x21,0x5e, 0x7f,0x09,0x09,0x19,0x66, 0x26,0x49,0x49,0x49,0x32, 0x01,0x01,0x7f,0x01,0x01, 0x3f,0x40,0x40,0x40,0x3f, 0x1f,0x20,0x40,0x20,0x1f, 0x3f,0x40,0x3c,0x40,0x3f, 0x63,0x14,0x08,0x14,0x63, 0x07,0x08,0x70,0x08,0x07, 0x71,0x49,0x45,0x43,0x00, 0x00,0x7f,0x41,0x41,0x00, 0x02,0x04,0x08,0x10,0x20, 0x00,0x41,0x41,0x7f,0x00, 0x04,0x02,0x01,0x02,0x04, 0x80,0x80,0x80,0x80,0x80, 0x00,0x03,0x07,0x00,0x00, 0x20,0x54,0x54,0x54,0x78, 0x7f,0x44,0x44,0x44,0x38, 0x38,0x44,0x44,0x44,0x28, 0x38,0x44,0x44,0x44,0x7f, 0x38,0x54,0x54,0x54,0x08, 0x08,0x7e,0x09,0x09,0x00, 0x18,0xa4,0xa4,0xa4,0x7c, 0x7f,0x04,0x04,0x78,0x00, 0x00,0x00,0x7d,0x40,0x00, 0x40,0x80,0x84,0x7d,0x00, 0x7f,0x10,0x28,0x44,0x00, 0x00,0x00,0x7f,0x40,0x00, 0x7c,0x04,0x18,0x04,0x78, 0x7c,0x04,0x04,0x78,0x00, 0x38,0x44,0x44,0x44,0x38, 0xfc,0x44,0x44,0x44,0x38, 0x38,0x44,0x44,0x44,0xfc, 0x44,0x78,0x44,0x04,0x08, 0x08,0x54,0x54,0x54,0x20, 0x04,0x3e,0x44,0x24,0x00, 0x3c,0x40,0x20,0x7c,0x00, 0x1c,0x20,0x40,0x20,0x1c, 0x3c,0x60,0x30,0x60,0x3c, 0x6c,0x10,0x10,0x6c,0x00, 0x9c,0xa0,0x60,0x3c,0x00, 0x64,0x54,0x54,0x4c,0x00, 0x08,0x3e,0x41,0x41,0x00, 0x00,0x00,0x77,0x00,0x00, 0x00,0x41,0x41,0x3e,0x08, 0x02,0x01,0x02,0x01,0x00, 0x3c,0x26,0x23,0x26,0x3c}; static void obdCachedFlush(OBDISP *pOBD, int bRender) { if (u8End > 0) { obdWriteDataBlock(pOBD, u8Cache, u8End, bRender); u8End = 0; } } /* obdCachedFlush() */ static void obdCachedWrite(OBDISP *pOBD, uint8_t *pData, uint8_t u8Len, int bRender) { if (u8End + u8Len > MAX_CACHE) // need to flush it { obdCachedFlush(pOBD, bRender); // write the old data } memcpy(&u8Cache[u8End], pData, u8Len); u8End += u8Len; } /* obdCachedWrite() */ // // Create a virtual display of any size // The memory buffer must be provided at the time of creation // void obdCreateVirtualDisplay(OBDISP *pOBD, int width, int height, uint8_t *buffer) { if (pOBD != NULL && buffer != NULL) { pOBD->width = width; pOBD->height = height; pOBD->type = LCD_VIRTUAL; pOBD->ucScreen = buffer; pOBD->iCursorX = pOBD->iCursorY = 0; pOBD->iScreenOffset = 0; } } /* obdCreateVirtualDisplay() */ // // Scroll the internal buffer by 1 scanline (up/down) // width is in pixels, lines is group of 8 rows // int obdScrollBuffer(OBDISP *pOBD, int iStartCol, int iEndCol, int iStartRow, int iEndRow, int bUp) { uint8_t b, *s; int col, row; int iPitch; if (iStartCol < 0 || iStartCol >= pOBD->width || iEndCol < 0 || iEndCol > pOBD->width || iStartCol > iEndCol) // invalid return OBD_ERROR_BAD_PARAMETER; if (iStartRow < 0 || iStartRow >= (pOBD->height/8) || iEndRow < 0 || iEndRow >= (pOBD->height/8) || iStartRow > iEndRow) return OBD_ERROR_BAD_PARAMETER; iPitch = pOBD->width; if (bUp) { for (row=iStartRow; row<=iEndRow; row++) { s = &pOBD->ucScreen[(row * iPitch) + iStartCol]; for (col=iStartCol; col<=iEndCol; col++) { b = *s; b >>= 1; // scroll pixels 'up' if (row < iEndRow) b |= (s[iPitch] << 7); // capture pixel of row below, except for last row *s++ = b; } // for col } // for row } // up else // down { for (row=iEndRow; row>=iStartRow; row--) { s = &pOBD->ucScreen[(row * iPitch)+iStartCol]; for (col=iStartCol; col<=iEndCol; col++) { b = *s; b <<= 1; // scroll down if (row > iStartRow) b |= (s[-iPitch] >> 7); // capture pixel of row above *s++ = b; } // for col } // for row } return OBD_SUCCESS; } /* obdScrollBuffer() */ // // Return the number of bytes accumulated as commands // int obdGetCommandLen(OBDISP *pOBD) { if (pOBD != NULL && pOBD->type == DISPLAY_COMMANDS) return pOBD->iScreenOffset; else return 0; } /* obdGetCommandLen() */ // // Write a single byte into the command queue // static void obdWriteCmdByte(OBDISP *pOBD, uint8_t u8) { uint8_t *d = pOBD->ucScreen; int i = pOBD->iScreenOffset; d[i++] = u8; pOBD->iScreenOffset = i; } /* obdWriteCmdByte() */ // // Write an unsigned integer into the command queue // use integer expansion to write small values as 1 byte // and larger values as 2 bytes // static void obdWriteCmdInt(OBDISP *pOBD, int iValue) { uint8_t *d = pOBD->ucScreen; int i = pOBD->iScreenOffset; if (iValue <= 127) { d[i++] = (uint8_t)iValue; } else { d[i++] = (uint8_t)(iValue | 0x80); // store lower 7 bits d[i++] = (uint8_t)(iValue >> 7); // store next 8 bits } pOBD->iScreenOffset = i; } /* obdWriteCmdInt() */ // // Read an unsigned integer from the command queue // use integer expansion to store small values as 1 byte // and larger values as 2 bytes // static uint8_t * obdReadCmdInt(uint8_t *pData, int *iValue) { int i; i = *pData++; if (i & 0x80) { // two byte integer i &= 0x7f; i |= (pData[0] << 7); pData++; } *iValue = i; return pData; } /* obdReadCmdInt() */ // // Set the current custom font pointers for playing back // bytewise commands // void obdSetCustomFont(OBDISP *pOBD, GFXfont *pFont, uint8_t ucFont) { if (pOBD != NULL && pFont != NULL && ucFont >= FONT_CUSTOM0 && ucFont <= FONT_CUSTOM2) { pOBD->pFont[ucFont-FONT_CUSTOM0] = (void *)pFont; } } /* obdSetCustomFont() */ // // Execute a set of bytewise command bytes // and execute the drawing instructions on the current display/buffer // Optionally render on backbuffer or physical display // void obdExecCommands(uint8_t *pData, int iLen, OBDISP *pOBD, int bRender) { uint8_t *s, *pEnd; uint8_t uc, ucColor=1, ucFill, ucFont; int x1, y1, x2, y2; int iTextLen, iPitch; uint8_t ucTemp[64]; if (pData == NULL || pOBD == NULL) return; s = pData; // source of the command data pEnd = &s[iLen]; while (s < pEnd-1) { uc = *s++; switch (uc & 0xf) { // lower 4 bits hold command case OBD_FILL: obdFill(pOBD, s[0], bRender); s++; break; case OBD_DRAWTEXT: iTextLen = *s++; ucColor = (uc >> 7); // invert flag ucFont = (uc >> 4) & 7; // font size if (pEnd - s >= iTextLen+3) { s = obdReadCmdInt(s, &x1); // col s = obdReadCmdInt(s, &y1); // row memcpy(ucTemp, s, iTextLen); ucTemp[iTextLen] = 0; // terminate the string s += iTextLen; if (ucFont >= FONT_CUSTOM0) { // up to 3 custom fonts if (pOBD->pFont[ucFont-FONT_CUSTOM0] != NULL) { obdWriteStringCustom(pOBD, (GFXfont *)pOBD->pFont[ucFont-FONT_CUSTOM0], x1, y1, (char *)ucTemp, ucColor); } } else { obdWriteString(pOBD, 0, x1, y1, (char *)ucTemp, ucFont, ucColor, bRender); } } else { return; // something went wrong! } break; case OBD_DRAWLINE: if (pEnd - s >= 4) { ucColor = (uc >> 4) & 1; s = obdReadCmdInt(s, &x1); s = obdReadCmdInt(s, &y1); s = obdReadCmdInt(s, &x2); s = obdReadCmdInt(s, &y2); obdDrawLine(pOBD, x1, y1, x2, y2, ucColor, bRender); } break; case OBD_DRAWRECT: if (pEnd - s >= 4) { ucColor = (uc >> 4) & 1; ucFill = (uc >> 5) & 1; s = obdReadCmdInt(s, &x1); s = obdReadCmdInt(s, &y1); s = obdReadCmdInt(s, &x2); s = obdReadCmdInt(s, &y2); obdRectangle(pOBD, x1, y1, x2, y2, ucColor, ucFill); } break; case OBD_DRAWELLIPSE: if (pEnd - s >= 4) { ucColor = (uc >> 4) & 1; ucFill = (uc >> 5) & 1; s = obdReadCmdInt(s, &x1); s = obdReadCmdInt(s, &y1); s = obdReadCmdInt(s, &x2); s = obdReadCmdInt(s, &y2); obdEllipse(pOBD, x1, y1, x2, y2, ucColor, ucFill); } break; case OBD_DRAWSPRITE: if (pEnd - s >= 8) { ucColor = (uc >> 4) & 1; s = obdReadCmdInt(s, &x1); // width / height s = obdReadCmdInt(s, &y1); s = obdReadCmdInt(s, &x2); // destination x/y s = obdReadCmdInt(s, &y2); iPitch = (x1 + 7) >> 3; if (pEnd - s >= (iPitch * y1)) { // enough to hold the data obdDrawSprite(pOBD, s, x1, y1, iPitch, x2, y2, ucColor); s += (iPitch * y1); } else { return; // error! } } break; default: return; // invalid command! } } } /* obdParseCommands() */ // // Draw a sprite of any size in any position // If it goes beyond the left/right or top/bottom edges // it's trimmed to show the valid parts // This function requires a back buffer to be defined // The priority color (0 or 1) determines which color is painted // when a 1 is encountered in the source image. // void obdDrawSprite(OBDISP *pOBD, uint8_t *pSprite, int cx, int cy, int iPitch, int x, int y, uint8_t iPriority) { int tx, ty, dx, dy, iStartX; uint8_t *s, *d, uc, pix, ucSrcMask, ucDstMask; int iLocalPitch; iLocalPitch = pOBD->width; if (pOBD == NULL) return; if (pOBD->type == DISPLAY_COMMANDS) { // encode this as a command sequence int iLocalPitch = (cx+7)>>3; obdWriteCmdByte(pOBD, OBD_DRAWSPRITE | ((iPriority & 1) << 4)); obdWriteCmdInt(pOBD, cx); obdWriteCmdInt(pOBD, cy); obdWriteCmdInt(pOBD, x); obdWriteCmdInt(pOBD, y); d = pOBD->ucScreen; tx = pOBD->iScreenOffset; s = pSprite; for (ty=0; tyiScreenOffset = tx; // store new length return; // done } if (x+cx < 0 || y+cy < 0 || x >= pOBD->width || y >= pOBD->height || pOBD->ucScreen == NULL) return; // no backbuffer or out of bounds dy = y; // destination y if (y < 0) // skip the invisible parts { cy += y; y = -y; pSprite += (y * iPitch); dy = 0; } if (y + cy > pOBD->height) cy = pOBD->height - y; iStartX = 0; dx = x; if (x < 0) { cx += x; x = -x; iStartX = x; dx = 0; } if (x + cx > pOBD->width) cx = pOBD->width - x; for (ty=0; ty> 3)]; d = &pOBD->ucScreen[(dy>>3) * iLocalPitch + dx]; ucSrcMask = 0x80 >> (iStartX & 7); pix = *s++; ucDstMask = 1 << (dy & 7); if (iPriority) // priority color is 1 { for (tx=0; tx>= 1; if (ucSrcMask == 0) // read next byte { ucSrcMask = 0x80; pix = *s++; } } // for tx } // priorty color 1 else { for (tx=0; tx>= 1; if (ucSrcMask == 0) // read next byte { ucSrcMask = 0x80; pix = *s++; } } // for tx } // priority color 0 dy++; pSprite += iPitch; } // for ty } /* obdDrawSprite() */ // // Draw a 16x16 tile in any of 4 rotated positions // Assumes input image is laid out like "normal" graphics with // the MSB on the left and 2 bytes per line // On AVR, the source image is assumed to be in FLASH memory // The function can draw the tile on byte boundaries, so the x value // can be from 0 to width-16 and y can be from 0 to (height/8)-2 // void obdDrawTile(OBDISP *pOBD, const uint8_t *pTile, int x, int y, int iRotation, int bInvert, int bRender) { uint8_t ucTemp[32]; // prepare LCD data here uint8_t i, j, k, iOffset, ucMask, uc, ucPixels; uint8_t bFlipX=0, bFlipY=0; if (x < 0 || y < 0 || y > (pOBD->height/8)-2 || x > pOBD->width-16) return; // out of bounds if (pTile == NULL) return; // bad pointer; really? :( if (iRotation == ANGLE_180 || iRotation == ANGLE_270 || iRotation == ANGLE_FLIPX) bFlipX = 1; if (iRotation == ANGLE_180 || iRotation == ANGLE_270 || iRotation == ANGLE_FLIPY) bFlipY = 1; memset(ucTemp, 0, sizeof(ucTemp)); // we only set white pixels, so start from black if (iRotation == ANGLE_0 || iRotation == ANGLE_180 || iRotation == ANGLE_FLIPX || iRotation == ANGLE_FLIPY) { for (j=0; j<16; j++) // y { for (i=0; i<16; i+=8) // x { ucPixels = pgm_read_byte((uint8_t*)pTile++); ucMask = 0x80; // MSB is the first source pixel for (k=0; k<8; k++) { if (ucPixels & ucMask) // translate the pixel { if (bFlipY) uc = 0x80 >> (j & 7); else uc = 1 << (j & 7); iOffset = i+k; if (bFlipX) iOffset = 15-iOffset; iOffset += (j & 8)<<1; // top/bottom half of output if (bFlipY) iOffset ^= 16; ucTemp[iOffset] |= uc; } ucMask >>= 1; } // for k } // for i } // for j } else // rotated 90/270 { for (j=0; j<16; j++) // y { for (i=0; i<16; i+=8) // x { ucPixels = pgm_read_byte((uint8_t*)pTile++); ucMask = 0x80; // MSB is the first source pixel for (k=0; k<8; k++) { if (ucPixels & ucMask) // translate the pixel { if (bFlipY) uc = 0x80 >> k; else uc = 1 << k; iOffset = 15-j; if (bFlipX) iOffset = 15-iOffset; iOffset += i<<1; // top/bottom half of output if (bFlipY) iOffset ^= 16; ucTemp[iOffset] |= uc; } ucMask >>= 1; } // for k } // for i } // for j } if (bInvert) InvertBytes(ucTemp, 32); // Send the data to the display obdSetPosition(pOBD, x, y, bRender); obdWriteDataBlock(pOBD, ucTemp, 16, bRender); // top half obdSetPosition(pOBD, x,y+1, bRender); obdWriteDataBlock(pOBD, &ucTemp[16], 16, bRender); // bottom half } /* obdDrawTile() */ // Set (or clear) an individual pixel // The local copy of the frame buffer is used to avoid // reading data from the display controller int obdSetPixel(OBDISP *pOBD, int x, int y, unsigned char ucColor, int bRender) { int i; unsigned char uc, ucOld; int iPitch, iSize; iPitch = pOBD->width; iSize = iPitch * ((pOBD->height+7)/8); i = ((y >> 3) * iPitch) + x; if (i < 0 || i > iSize-1) { // off the screen return OBD_ERROR_BAD_PARAMETER; } // Special case for 3-color e-ink if (pOBD->iFlags & OBD_3COLOR) { if (ucColor == OBD_RED) { // red has priority pOBD->ucScreen[iSize + i] |= (1 << (y & 7)); } else { pOBD->ucScreen[iSize + i] &= ~(1 << (y & 7)); // clear red plane bit if (ucColor == OBD_WHITE) { pOBD->ucScreen[i] &= ~(1 << (y & 7)); } else { // must be black pOBD->ucScreen[i] |= (1 << (y & 7)); } } return OBD_SUCCESS; } obdSetPosition(pOBD, x, y, bRender); if (pOBD->ucScreen) uc = ucOld = pOBD->ucScreen[i]; #ifndef MEMORY_ONLY else if (pOBD->type == OLED_132x64 || pOBD->type == OLED_128x128) // SH1106/SH1107 can read data { uint8_t ucTemp[3]; ucTemp[0] = 0x80; // one command ucTemp[1] = 0xE0; // read_modify_write ucTemp[2] = 0xC0; // one data RawWrite(pOBD, ucTemp, 3); // read a dummy byte followed by the data byte we want I2CRead(&pOBD->bbi2c, pOBD->oled_addr, ucTemp, 2); uc = ucOld = ucTemp[1]; // first byte is garbage } #endif // MEMORY_ONLY else uc = ucOld = 0; uc &= ~(0x1 << (y & 7)); if (ucColor) { uc |= (0x1 << (y & 7)); } if (uc != ucOld) // pixel changed { // obdSetPosition(x, y>>3); if (pOBD->ucScreen) { obdWriteDataBlock(pOBD, &uc, 1, bRender); pOBD->ucScreen[i] = uc; } else if (pOBD->type == OLED_132x64 || pOBD->type == OLED_128x128) // end the read_modify_write operation { uint8_t ucTemp[4]; ucTemp[0] = 0xc0; // one data ucTemp[1] = uc; // actual data ucTemp[2] = 0x80; // one command ucTemp[3] = 0xEE; // end read_modify_write operation RawWrite(pOBD, ucTemp, 4); } } return OBD_ERROR_BAD_PARAMETER; } /* obdSetPixel() */ // // Invert font data // void InvertBytes(uint8_t *pData, uint8_t bLen) { uint8_t i; for (i=0; iucScreen == NULL) { // no BG specified or no back buffer, BG color is opposite of FG if (iFG == -1) iFG = OBD_WHITE; if (iBG == -1) iBG = OBD_BLACK; } // Don't use pgm_read_word because it can cause an unaligned // access on RP2040 for odd addresses i16 = pgm_read_byte(pBMP); i16 += (pgm_read_byte(&pBMP[1]) << 8); if (i16 != 0x4d42) // must start with 'BM' return OBD_ERROR_BAD_DATA; // not a BMP file cx = pgm_read_byte(pBMP + 18); cx += (pgm_read_byte(pBMP+19)<<8); if (cx + dx > pOBD->width) // must fit on the display return OBD_ERROR_BAD_PARAMETER; cy = pgm_read_byte(pBMP + 22); cy += (pgm_read_byte(pBMP+23)<<8); if (cy < 0) cy = -cy; else bFlipped = 1; if (cy + dy > pOBD->height) // must fit on the display return OBD_ERROR_BAD_PARAMETER; i16 = pgm_read_byte(pBMP + 28); i16 += (pgm_read_byte(pBMP+29)<<8); if (i16 != 1) // must be 1 bit per pixel return OBD_ERROR_BAD_DATA; iOffBits = pgm_read_byte(pBMP + 10); iOffBits += (pgm_read_byte(pBMP+11)); iPitch = (((cx+7)>>3) + 3) & 0xfffc; // must be DWORD aligned if (bFlipped) { iOffBits += ((cy-1) * iPitch); // start from bottom iPitch = -iPitch; } ucFill = (iBG == OBD_WHITE && pOBD->type < EPD42_400x300) ? iBG : 0xff; if (!pOBD->ucScreen || iFG == OBD_RED) { // this will override the B/W plane, so invert things ucFill = 0x00; x = iFG; iFG = iBG; iBG = x; // swap colors } for (y=0; yucScreen) { dst_mask = (1 << (y & 7)); d = u8Cache; if ((y & 7) == 0) memset(u8Cache, ucFill, sizeof(u8Cache)); } s = &pBMP[iOffBits + (y*iPitch)]; src_mask = 0; if (!pOBD->ucScreen) // direct to display { for (x=0; x>= 1; } // for x } else { // use the setPixel function for more features for (x=0; x= 0) obdSetPixel(pOBD, dx+x, dy+y, (uint8_t)iFG, 0); } else { if (iBG >= 0) obdSetPixel(pOBD, dx+x, dy+y, (uint8_t)iBG, 0); } src_mask >>= 1; } // for x } if (pOBD->ucScreen == NULL && ((y & 7) == 7 || y == cy-1)) // dump to LCD { obdSetPosition(pOBD, dx, y+dy, 1); obdWriteDataBlock(pOBD, u8Cache, cx, 1); } } // for y return OBD_SUCCESS; } /* obdLoadBMP() */ // // Load a 4-bpp Windows bitmap for a 3-color bitmap // Pass the pointer to the beginning of the BMP file // First pass version assumes a full screen bitmap // int obdLoadBMP3(OBDISP *pOBD, uint8_t *pBMP, int dx, int dy) { int16_t i16, cx, cy, bpp; int x, y, iOffBits; // offset to bitmap data int iPitch, iDestPitch; int iRedOff, iColors, iPalOff; uint8_t uc, b, *s, *d; uint8_t dst_mask; uint8_t bFlipped = 0; uint8_t ucColorMap[16]; if (!(pOBD->iFlags & OBD_3COLOR) || pOBD->ucScreen == 0) return OBD_ERROR_NOT_SUPPORTED; // if not 3-color EPD or no back buffer, bye-byte iDestPitch = pOBD->width; iRedOff = ((pOBD->height+7)>>3) * iDestPitch; // Need to avoid pgm_read_word because it can cause an // unaligned address exception on the RP2040 for odd addresses i16 = pgm_read_byte(pBMP); i16 += (pgm_read_byte(pBMP+1)<<8); if (i16 != 0x4d42) // must start with 'BM' return OBD_ERROR_BAD_DATA; // not a BMP file cx = pgm_read_byte(&pBMP[18]); cx += (pgm_read_byte(&pBMP[19]) << 8); if (cx + dx > pOBD->width) // must fit on the display return OBD_ERROR_BAD_PARAMETER; cy = pgm_read_byte(&pBMP[22]); cy += (pgm_read_byte(&pBMP[23])<<8); if (cy < 0) cy = -cy; else bFlipped = 1; if (cy + dy > pOBD->height) // must fit on the display return OBD_ERROR_BAD_PARAMETER; if (pgm_read_byte(&pBMP[30]) != 0) // compression must be NONE return OBD_ERROR_BAD_DATA; bpp = pgm_read_byte(&pBMP[28]); if (bpp != 4) // must be 4 bits per pixel return OBD_ERROR_BAD_DATA; iOffBits = pgm_read_byte(&pBMP[10]); iOffBits += (pgm_read_byte(&pBMP[11])<<8); iColors = pgm_read_byte(&pBMP[46]); // colors used BMP field if (iColors == 0 || iColors > (1<>1) + 3) & 0xfffc; // must be DWORD aligned if (bFlipped) { iOffBits += ((cy-1) * iPitch); // start from bottom iPitch = -iPitch; } // Map the colors to white/black/red with a simple quantization. Convert colors to G3R3B2 and find the closest value (red in the middle) // white = 0xff, red = 0x1c, black = 0x00 for (x=0; x> 6) | ((r >> 5) << 2) | ((g >> 5) << 5); if (uc >= 0x1c) { // check for red/white ucColorMap[x] = ((0xff - uc) < (uc - 0x1c)) ? OBD_WHITE : OBD_RED; } else { ucColorMap[x] = ((0x1c - uc) < uc) ? OBD_RED : OBD_BLACK; } } for (y=0; yucScreen[(((y+dy)>>3)*iDestPitch)+dx]; s = &pBMP[iOffBits+(y*iPitch)]; for (x=0; x> 4]; // left pixel if (uc == OBD_BLACK) d[x] |= dst_mask; else if (uc == OBD_RED) d[x+iRedOff] |= dst_mask; // we made it white already uc = ucColorMap[b & 0xf]; // right pixel if (uc == OBD_BLACK) d[x+1] |= dst_mask; else if (uc == OBD_RED) d[x+1+iRedOff] |= dst_mask; } // for x } // for y return OBD_SUCCESS; } /* obdLoadBMP3() */ // // Set the current cursor position // The column represents the pixel column (0-127) // The row represents the text row (0-7) // void obdSetCursor(OBDISP *pOBD, int x, int y) { pOBD->iCursorX = x; pOBD->iCursorY = y; } /* obdSetCursor() */ // // Advance to the next line // void obdNextLine(OBDISP *pOBD) { pOBD->iCursorX = 0; pOBD->iCursorY++; } /* obdNextLine() */ // // Turn text wrap on or off for the oldWriteString() function // void obdSetTextWrap(OBDISP *pOBD, int bWrap) { pOBD->wrap = bWrap; } /* obdSetTextWrap() */ // // Draw a string with a fractional scale in both dimensions // the scale is a 16-bit integer with and 8-bit fraction and 8-bit mantissa // To draw at 1x scale, set the scale factor to 256. To draw at 2x, use 512 // The output must be drawn into a memory buffer, not directly to the display // int obdScaledString(OBDISP *pOBD, int x, int y, char *szMsg, int iSize, int iColor, int iXScale, int iYScale, int iRotation) { uint32_t row, col, dx, dy; uint32_t sx, sy; uint8_t c, uc, color; const uint8_t *s; uint8_t ucTemp[16]; int tx, ty, bit, iFontOff; int iFontWidth; if (iXScale == 0 || iYScale == 0 || szMsg == NULL || pOBD == NULL || pOBD->ucScreen == NULL || x < 0 || y < 0 || x >= pOBD->width-1 || y >= pOBD->height-1) return OBD_ERROR_BAD_PARAMETER; // invalid display structure if (iSize != FONT_8x8 && iSize != FONT_6x8) return OBD_ERROR_BAD_PARAMETER; // only on the small fonts (for now) iFontWidth = (iSize == FONT_6x8) ? 6:8; s = (iSize == FONT_6x8) ? ucSmallFont : ucFont; dx = (iFontWidth * iXScale) >> 8; // width of each character dy = (8 * iYScale) >> 8; // height of each character sx = 65536 / iXScale; // turn the scale into an accumulator value sy = 65536 / iYScale; while (*szMsg) { c = *szMsg++; // debug - start with normal font iFontOff = (int)(c-32) * (iFontWidth-1); // we can't directly use the pointer to FLASH memory, so copy to a local buffer ucTemp[0] = 0; // first column is blank memcpy_P(&ucTemp[1], &s[iFontOff], iFontWidth-1); // if (iColor == OBD_WHITE) InvertBytes(ucTemp, iFontWidth); col = 0; for (tx=0; tx<(int)dx; tx++) { row = 0; uc = ucTemp[col >> 8]; for (ty=0; ty<(int)dy; ty++) { int nx=0, ny=0; bit = row >> 8; color = (uc & (1 << bit)); // set or clear the pixel switch (iRotation) { case ROT_0: nx = x + tx; ny = y + ty; break; case ROT_90: nx = x - ty; ny = y + tx; break; case ROT_180: nx = x - tx; ny = y - ty; break; case ROT_270: nx = x + ty; ny = y - tx; break; } // switch on rotation direction // plot the pixel if it's within the image boundaries if (color) obdSetPixel(pOBD, nx, ny, pOBD->iFG, iColor); row += sy; // add fractional increment to source row of character } // for ty col += sx; // add fractional increment to source column } // for tx // update the 'cursor' position switch (iRotation) { case ROT_0: x += dx; break; case ROT_90: y += dx; break; case ROT_180: x -= dx; break; case ROT_270: y -= dx; break; } // switch on rotation } // while (*szMsg) return OBD_SUCCESS; } /* obdScaledString() */ // // Draw a string of normal (8x8), small (6x8) or large (16x32) characters // At the given col+row // int obdWriteString(OBDISP *pOBD, int iScroll, int x, int y, char *szMsg, int iSize, int iColor, int bRender) { int i, iFontOff, iLen, iFontSkip; unsigned char c, *s; int iOldFG; // old fg color to make sure red works if (pOBD == NULL) return OBD_ERROR_BAD_PARAMETER; if (pOBD->type == DISPLAY_COMMANDS) { // encode this as a command sequence uint8_t *d = pOBD->ucScreen; iLen = (int)strlen(szMsg); obdWriteCmdByte(pOBD, OBD_DRAWTEXT | ((iColor & 1) << 7) | ((iSize & 7) << 4)); obdWriteCmdByte(pOBD, (uint8_t) iLen); obdWriteCmdInt(pOBD, x); obdWriteCmdInt(pOBD, y); i = pOBD->iScreenOffset; memcpy(&d[i], szMsg, iLen); i += iLen; pOBD->iScreenOffset = i; // store new length return OBD_SUCCESS; // done } // e-paper color is inverted compared to OLED/LCD. If we're in bufferless mode, we'll need to // invert the requested color if (pOBD->type >= EPD42_400x300 && !pOBD->ucScreen) { iColor = 1 - iColor; // invert the color } if (x == -1 || y == -1) // use the cursor position { x = pOBD->iCursorX; y = pOBD->iCursorY; } else { pOBD->iCursorX = x; pOBD->iCursorY = y; } if (pOBD->iCursorX >= pOBD->width || pOBD->iCursorY >= pOBD->height) return OBD_ERROR_BAD_PARAMETER; // can't draw off the display obdSetPosition(pOBD, pOBD->iCursorX, pOBD->iCursorY, bRender); iOldFG = pOBD->iFG; // save old fg color if (iColor == OBD_RED) { pOBD->iFG = iColor; } if (iSize == FONT_8x8) // 8x8 font { i = 0; iFontSkip = iScroll & 7; // number of columns to initially skip while (pOBD->iCursorX < pOBD->width && szMsg[i] != 0 && pOBD->iCursorY < pOBD->height) { if (iScroll < 8) // only display visible characters { c = (unsigned char)szMsg[i]; iFontOff = (int)(c-32) * 7; // we can't directly use the pointer to FLASH memory, so copy to a local buffer u8Temp[0] = 0; // first column is blank memcpy_P(&u8Temp[1], &ucFont[iFontOff], 7); if (iColor == OBD_WHITE) InvertBytes(u8Temp, 8); iLen = 8 - iFontSkip; if (pOBD->iCursorX + iLen > pOBD->width) // clip right edge iLen = pOBD->width - pOBD->iCursorX; obdCachedWrite(pOBD, &u8Temp[iFontSkip], iLen, bRender); // obdWriteDataBlock(pOBD, &ucTemp[iFontSkip], iLen, bRender); // write character pattern pOBD->iCursorX += iLen; if (pOBD->iCursorX >= pOBD->width-7 && pOBD->wrap) // word wrap enabled? { obdCachedFlush(pOBD, bRender); pOBD->iCursorX = 0; // start at the beginning of the next line pOBD->iCursorY+=8; obdSetPosition(pOBD, pOBD->iCursorX, pOBD->iCursorY, bRender); } iFontSkip = 0; } iScroll -= 8; i++; } // while obdCachedFlush(pOBD, bRender); // write any remaining data pOBD->iFG = iOldFG; // restore color return OBD_SUCCESS; } // 8x8 #ifndef WIMPY_MCU else if (iSize == FONT_16x32) { i = 0; iFontSkip = iScroll & 15; // number of columns to initially skip while (pOBD->iCursorX < pOBD->width && pOBD->iCursorY < pOBD->height && szMsg[i] != 0) { if (iScroll < 16) // if characters are visible { s = (unsigned char *)&ucBigFont[(unsigned char)(szMsg[i]-32)*64]; iLen = 16 - iFontSkip; if (pOBD->iCursorX + iLen > pOBD->width) // clip right edge iLen = pOBD->width - pOBD->iCursorX; // we can't directly use the pointer to FLASH memory, so copy to a local buffer obdSetPosition(pOBD, pOBD->iCursorX, pOBD->iCursorY, bRender); memcpy_P(u8Cache, s, 16); if (iColor == OBD_WHITE) InvertBytes(u8Cache, 16); obdWriteDataBlock(pOBD, &u8Cache[iFontSkip], iLen, bRender); // write character pattern obdSetPosition(pOBD, pOBD->iCursorX, pOBD->iCursorY+8, bRender); memcpy_P(u8Cache, s+16, 16); if (iColor == OBD_WHITE) InvertBytes(u8Cache, 16); obdWriteDataBlock(pOBD, &u8Cache[iFontSkip], iLen, bRender); // write character pattern obdSetPosition(pOBD, pOBD->iCursorX, pOBD->iCursorY+16, bRender); memcpy_P(u8Cache, s+32, 16); if (iColor == OBD_WHITE) InvertBytes(u8Cache, 16); obdWriteDataBlock(pOBD, &u8Cache[iFontSkip], iLen, bRender); // write character pattern obdSetPosition(pOBD, pOBD->iCursorX, pOBD->iCursorY+24, bRender); memcpy_P(u8Cache, s+48, 16); if (iColor == OBD_WHITE) InvertBytes(u8Cache, 16); obdWriteDataBlock(pOBD, &u8Cache[iFontSkip], iLen, bRender); // write character pattern pOBD->iCursorX += iLen; if (pOBD->iCursorX >= pOBD->width-15 && pOBD->wrap) // word wrap enabled? { pOBD->iCursorX = 0; // start at the beginning of the next line pOBD->iCursorY+=32; } iFontSkip = 0; } // if character visible from scrolling iScroll -= 16; i++; } // while pOBD->iFG = iOldFG; // restore color return OBD_SUCCESS; } // 16x32 #endif // !WIMPY_MCU else if (iSize == FONT_16x16) // 8x8 stretched to 16x16 { i = 0; iFontSkip = iScroll & 15; // number of columns to initially skip while (pOBD->iCursorX < pOBD->width && pOBD->iCursorY < pOBD->height && szMsg[i] != 0) { // stretch the 'normal' font instead of using the big font if (iScroll < 16) // if characters are visible { int tx, ty; c = szMsg[i] - 32; unsigned char uc1, uc2, ucMask, *pDest; s = (unsigned char *)&ucFont[(int)c*7]; u8Temp[0] = 0; // first column is blank memcpy_P(&u8Temp[1], s, 7); if (iColor == OBD_WHITE) InvertBytes(u8Temp, 8); // Stretch the font to double width + double height memset(&u8Temp[8], 0, 32); // write 32 new bytes for (tx=0; tx<8; tx++) { ucMask = 3; pDest = &u8Temp[8+tx*2]; uc1 = uc2 = 0; c = u8Temp[tx]; for (ty=0; ty<4; ty++) { if (c & (1 << ty)) // a bit is set uc1 |= ucMask; if (c & (1 << (ty + 4))) uc2 |= ucMask; ucMask <<= 2; } pDest[0] = uc1; pDest[1] = uc1; // double width pDest[16] = uc2; pDest[17] = uc2; } iLen = 16 - iFontSkip; if (pOBD->iCursorX + iLen > pOBD->width) // clip right edge iLen = pOBD->width - pOBD->iCursorX; obdSetPosition(pOBD, pOBD->iCursorX, pOBD->iCursorY, bRender); obdWriteDataBlock(pOBD, &u8Temp[8+iFontSkip], iLen, bRender); obdSetPosition(pOBD, pOBD->iCursorX, pOBD->iCursorY+8, bRender); obdWriteDataBlock(pOBD, &u8Temp[24+iFontSkip], iLen, bRender); pOBD->iCursorX += iLen; if (pOBD->iCursorX >= pOBD->width-15 && pOBD->wrap) // word wrap enabled? { pOBD->iCursorX = 0; // start at the beginning of the next line pOBD->iCursorY += 16; obdSetPosition(pOBD, pOBD->iCursorX, pOBD->iCursorY, bRender); } iFontSkip = 0; } // if characters are visible iScroll -= 16; i++; } // while pOBD->iFG = iOldFG; // restore color return OBD_SUCCESS; } // 16x16 else if (iSize == FONT_12x16) // 6x8 stretched to 12x16 { i = 0; iFontSkip = iScroll % 12; // number of columns to initially skip while (pOBD->iCursorX < pOBD->width && pOBD->iCursorY < pOBD->height && szMsg[i] != 0) { // stretch the 'normal' font instead of using the big font if (iScroll < 12) // if characters are visible { int tx, ty; c = szMsg[i] - 32; unsigned char uc1, uc2, ucMask, *pDest; s = (unsigned char *)&ucSmallFont[(int)c*5]; u8Temp[0] = 0; // first column is blank memcpy_P(&u8Temp[1], s, 6); if (iColor == OBD_WHITE) InvertBytes(u8Temp, 6); // Stretch the font to double width + double height memset(&u8Temp[6], 0, 24); // write 24 new bytes for (tx=0; tx<6; tx++) { ucMask = 3; pDest = &u8Temp[6+tx*2]; uc1 = uc2 = 0; c = u8Temp[tx]; for (ty=0; ty<4; ty++) { if (c & (1 << ty)) // a bit is set uc1 |= ucMask; if (c & (1 << (ty + 4))) uc2 |= ucMask; ucMask <<= 2; } pDest[0] = uc1; pDest[1] = uc1; // double width pDest[12] = uc2; pDest[13] = uc2; } // smooth the diagonal lines for (tx=0; tx<5; tx++) { uint8_t c0, c1, ucMask2; c0 = u8Temp[tx]; c1 = u8Temp[tx+1]; pDest = &u8Temp[6+tx*2]; ucMask = 1; ucMask2 = 2; for (ty=0; ty<7; ty++) { if (((c0 & ucMask) && !(c1 & ucMask) && !(c0 & ucMask2) && (c1 & ucMask2)) || (!(c0 & ucMask) && (c1 & ucMask) && (c0 & ucMask2) && !(c1 & ucMask2))) { if (ty < 3) // top half { if (iColor == OBD_WHITE) { pDest[1] &= ~(1 << ((ty * 2)+1)); pDest[2] &= ~(1 << ((ty * 2)+1)); pDest[1] &= ~(1 << ((ty+1) * 2)); pDest[2] &= ~(1 << ((ty+1) * 2)); } else { pDest[1] |= (1 << ((ty * 2)+1)); pDest[2] |= (1 << ((ty * 2)+1)); pDest[1] |= (1 << ((ty+1) * 2)); pDest[2] |= (1 << ((ty+1) * 2)); } } else if (ty == 3) // on the border { if (iColor == OBD_WHITE) { pDest[1] &= ~0x80; pDest[2] &= ~0x80; pDest[13] &= ~1; pDest[14] &= ~1; } else { pDest[1] |= 0x80; pDest[2] |= 0x80; pDest[13] |= 1; pDest[14] |= 1; } } else // bottom half { if (iColor == OBD_WHITE) { pDest[13] &= ~(1 << (2*(ty-4)+1)); pDest[14] &= ~(1 << (2*(ty-4)+1)); pDest[13] &= ~(1 << ((ty-3) * 2)); pDest[14] &= ~(1 << ((ty-3) * 2)); } else { pDest[13] |= (1 << (2*(ty-4)+1)); pDest[14] |= (1 << (2*(ty-4)+1)); pDest[13] |= (1 << ((ty-3) * 2)); pDest[14] |= (1 << ((ty-3) * 2)); } } } else if (!(c0 & ucMask) && (c1 & ucMask) && (c0 & ucMask2) && !(c1 & ucMask2)) { if (ty < 4) // top half { if (iColor == OBD_WHITE) { pDest[1] &= ~(1 << ((ty * 2)+1)); pDest[2] &= ~(1 << ((ty+1) * 2)); } else { pDest[1] |= (1 << ((ty * 2)+1)); pDest[2] |= (1 << ((ty+1) * 2)); } } else { if (iColor == OBD_WHITE) { pDest[13] &= ~(1 << (2*(ty-4)+1)); pDest[14] &= ~(1 << ((ty-3) * 2)); } else { pDest[13] |= (1 << (2*(ty-4)+1)); pDest[14] |= (1 << ((ty-3) * 2)); } } } ucMask <<= 1; ucMask2 <<= 1; } } iLen = 12 - iFontSkip; if (pOBD->iCursorX + iLen > pOBD->width) // clip right edge iLen = pOBD->width - pOBD->iCursorX; obdSetPosition(pOBD, pOBD->iCursorX, pOBD->iCursorY, bRender); obdWriteDataBlock(pOBD, &u8Temp[6+iFontSkip], iLen, bRender); obdSetPosition(pOBD, pOBD->iCursorX, pOBD->iCursorY+8, bRender); obdWriteDataBlock(pOBD, &u8Temp[18+iFontSkip], iLen, bRender); pOBD->iCursorX += iLen; if (pOBD->iCursorX >= pOBD->width-11 && pOBD->wrap) // word wrap enabled? { pOBD->iCursorX = 0; // start at the beginning of the next line pOBD->iCursorY += 16; obdSetPosition(pOBD, pOBD->iCursorX, pOBD->iCursorY, bRender); } iFontSkip = 0; } // if characters are visible iScroll -= 12; i++; } // while pOBD->iFG = iOldFG; // restore color return OBD_SUCCESS; } // 12x16 else if (iSize == FONT_6x8) { i = 0; iFontSkip = iScroll % 6; while (pOBD->iCursorX < pOBD->width && pOBD->iCursorY < pOBD->height && szMsg[i] != 0) { if (iScroll < 6) // if characters are visible { c = szMsg[i] - 32; // we can't directly use the pointer to FLASH memory, so copy to a local buffer u8Temp[0] = 0; // first column is blank memcpy_P(&u8Temp[1], &ucSmallFont[(int)c*5], 5); if (iColor == OBD_WHITE) InvertBytes(u8Temp, 6); iLen = 6 - iFontSkip; if (pOBD->iCursorX + iLen > pOBD->width) // clip right edge iLen = pOBD->width - pOBD->iCursorX; obdCachedWrite(pOBD, &u8Temp[iFontSkip], iLen, bRender); // obdWriteDataBlock(pOBD, &ucTemp[iFontSkip], iLen, bRender); // write character pattern pOBD->iCursorX += iLen; iFontSkip = 0; if (pOBD->iCursorX >= pOBD->width-5 && pOBD->wrap) // word wrap enabled? { obdCachedFlush(pOBD, bRender); pOBD->iCursorX = 0; // start at the beginning of the next line pOBD->iCursorY +=8; obdSetPosition(pOBD, pOBD->iCursorX, pOBD->iCursorY, bRender); } } // if characters are visible iScroll -= 6; i++; } obdCachedFlush(pOBD, bRender); // write any remaining data pOBD->iFG = iOldFG; // restore color return OBD_SUCCESS; } // 6x8 pOBD->iFG = iOldFG; // restore color return OBD_ERROR_BAD_PARAMETER; // invalid size } /* obdWriteString() */ // // Get the width of text in a custom font // void obdGetStringBox(GFXfont *pFont, char *szMsg, int *width, int *top, int *bottom) { int cx = 0; unsigned int c, i = 0; GFXglyph *pGlyph; int miny, maxy; if (width == NULL || top == NULL || bottom == NULL || pFont == NULL || szMsg == NULL) return; // bad pointers miny = 100; maxy = 0; while (szMsg[i]) { c = szMsg[i++]; if (c < pFont->first || c > pFont->last) // undefined character continue; // skip it c -= pFont->first; // first char of font defined pGlyph = &pFont->glyph[c]; cx += pGlyph->xAdvance; if (pGlyph->yOffset < miny) miny = pGlyph->yOffset; if (pGlyph->height+pGlyph->yOffset > maxy) maxy = pGlyph->height+pGlyph->yOffset; } *width = cx; *top = miny; *bottom = maxy; } /* obdGetStringBox() */ // // Draw a string of characters in a custom font // A back buffer must be defined // int obdWriteStringCustom(OBDISP *pOBD, GFXfont *pFont, int x, int y, char *szMsg, uint8_t ucColor) { int i, end_y, dx, dy, tx, ty, iBitOff; unsigned int c; uint8_t *s, *d, bits, ucFill=0, ucMask, uc; GFXfont font; GFXglyph glyph, *pGlyph; int iPitch, iRedOffset = 0; if (pOBD == NULL || pFont == NULL) return OBD_ERROR_BAD_PARAMETER; if (pOBD->ucScreen == NULL && pOBD->type >= EPD42_400x300) { // EPD direct draw mode; colors are inverted if (ucColor == OBD_BLACK) ucColor = 1-ucColor; ucFill = 0xff; } if (x == -1) x = pOBD->iCursorX; if (y == -1) y = pOBD->iCursorY; if (ucColor == OBD_RED && pOBD->iFlags & OBD_3COLOR) { // use the second half of the image buffer iRedOffset = pOBD->width * ((pOBD->height+7)/8); ucFill = 0x00; ucColor = OBD_BLACK; } if (pOBD->type == DISPLAY_COMMANDS) { // encode this as a command sequence uint8_t *d = pOBD->ucScreen; dx = (int)strlen(szMsg); // The font pointer is really the integer font index i = (int)pFont; if (i >= FONT_CUSTOM0) { // must be a valid index obdWriteCmdByte(pOBD, OBD_DRAWTEXT | ((ucColor & 1) << 7) | ((i & 7) << 4)); obdWriteCmdByte(pOBD, (uint8_t) dx); obdWriteCmdInt(pOBD, x); obdWriteCmdInt(pOBD, y); i = pOBD->iScreenOffset; memcpy(&d[i], szMsg, dx); i += dx; pOBD->iScreenOffset = i; // store new length } return OBD_SUCCESS; // done } iPitch = pOBD->width; // in case of running on Harvard architecture, get copy of data from FLASH memcpy_P(&font, pFont, sizeof(font)); pGlyph = &glyph; i = 0; while (szMsg[i] && x < pOBD->width) { c = szMsg[i++]; if (c < font.first || c > font.last) // undefined character continue; // skip it c -= font.first; // first char of font defined memcpy_P(&glyph, &font.glyph[c], sizeof(glyph)); dx = x + pGlyph->xOffset; // offset from character UL to start drawing dy = y + pGlyph->yOffset; s = font.bitmap + pGlyph->bitmapOffset; // start of bitmap data // Bitmap drawing loop. Image is MSB first and each pixel is packed next // to the next (continuing on to the next character line) iBitOff = 0; // bitmap offset (in bits) bits = uc = 0; // bits left in this font byte end_y = dy + pGlyph->height; if (dy < 0) { // skip these lines iBitOff += (pGlyph->width * (-dy)); dy = 0; } if (!pOBD->ucScreen) { memset(u8Cache, ucFill, sizeof(u8Cache)); } for (ty=dy; tyheight; ty++) { ucMask = 1<<(ty & 7); // destination bit number for this line if (pOBD->ucScreen) { d = &pOBD->ucScreen[iRedOffset + (ty >> 3) * iPitch + dx]; // internal buffer dest } else { d = u8Cache; // no ram; buffer 8 lines at a time } for (tx=0; txwidth; tx++) { if (bits == 0) { // need to read more font data uc = pgm_read_byte(&s[iBitOff>>3]); // get more font bitmap data bits = 8 - (iBitOff & 7); // we might not be on a byte boundary iBitOff += bits; // because of a clipped line uc <<= (8-bits); } // if we ran out of bits if ((dx+tx) < pOBD->width) { // foreground pixel if (uc & 0x80) { if (ucColor == OBD_BLACK) d[tx] |= ucMask; else d[tx] &= ~ucMask; } else { if (ucColor == OBD_BLACK) d[tx] &= ~ucMask; else d[tx] |= ucMask; } } bits--; // next bit uc <<= 1; } // for x if (!pOBD->ucScreen && (ucMask == 0x80 || ty == end_y-1)) { // dump this line obdSetPosition(pOBD, dx, (ty & 0xfff8), 1); obdWriteDataBlock(pOBD, u8Cache, pGlyph->width, 1); memset(u8Cache, ucFill, sizeof(u8Cache)); // NB: assume no DMA } } // for y x += pGlyph->xAdvance; // width of this character } // while drawing characters pOBD->iCursorX = x; pOBD->iCursorY = y; return OBD_SUCCESS; } /* obdWriteStringCustom() */ // // Render a sprite/rectangle of pixels from a provided buffer to the display. // The row values refer to byte rows, not pixel rows due to the memory // layout of OLEDs and LCDs. // returns 0 for success, -1 for invalid parameter // int obdDrawGFX(OBDISP *pOBD, uint8_t *pBuffer, int iSrcCol, int iSrcRow, int iDestCol, int iDestRow, int iWidth, int iHeight, int iSrcPitch) { int y; if (iSrcCol < 0 || iSrcCol >= pOBD->width || iSrcRow < 0 || iSrcRow > (pOBD->height/8)-1 || iDestCol < 0 || iDestCol >= pOBD->width || iDestRow < 0 || iDestRow >= (pOBD->height >> 3) || iSrcPitch <= 0) return OBD_ERROR_BAD_PARAMETER; // invalid for (y=iSrcRow; ytype == DISPLAY_COMMANDS) { // encode this as a command sequence obdWriteCmdByte(pOBD, OBD_FILL); obdWriteCmdByte(pOBD, ucData); return; } pOBD->iCursorX = pOBD->iCursorY = 0; if (pOBD->type == LCD_VIRTUAL || pOBD->type >= SHARP_144x168) // special display types { if (pOBD->ucScreen) { int iSize = pOBD->width * ((pOBD->height+7)/8); if (ucData == OBD_WHITE) { memset(pOBD->ucScreen, ucData, iSize); if (pOBD->iFlags & OBD_3COLOR) memset(&pOBD->ucScreen[iSize], ucData, iSize); } else if (ucData == OBD_BLACK) { memset(pOBD->ucScreen, 0xff, iSize); if (pOBD->iFlags & OBD_3COLOR) memset(&pOBD->ucScreen[iSize], 0, iSize); } else if (ucData == OBD_RED) { memset(pOBD->ucScreen, 0, iSize); if (pOBD->iFlags & OBD_3COLOR) memset(&pOBD->ucScreen[iSize], 0xff, iSize); } else { // write pattern if (pOBD->iFlags & OBD_3COLOR) iSize *= 2; memset(pOBD->ucScreen, ucData, iSize); } } #ifndef MEMORY_ONLY else if (pOBD->type >= EPD42_400x300) { uint8_t ucPattern1 = 0xff, ucPattern2 = 0xff; // assume white uint8_t ucRAM1, ucRAM2; int iOldRotation; if (pOBD->iFlags & OBD_3COLOR) { ucPattern2 = 0x00; // red plane is not inverted if (ucData == OBD_BLACK) ucPattern1 = 0x00; else if (ucData == OBD_RED) ucPattern2 = 0xff; } else { // black/white if (ucData == OBD_BLACK) ucPattern1 = ucPattern2 = 0x00; } ucRAM1 = (pOBD->chip_type == OBD_CHIP_UC8151) ? (uint8_t)UC8151_DTM1 : (uint8_t)SSD1608_WRITE_RAM; ucRAM2 = (pOBD->chip_type == OBD_CHIP_UC8151) ? (uint8_t)UC8151_DTM2 : (uint8_t)SSD1608_WRITE_ALTRAM; // Force 0 rotation because bufferless operation // will miss pixel rows not a multiple of 8 // when rotated 90 (e.g. 122x250 resolution) iOldRotation = pOBD->iOrientation; pOBD->iOrientation = 0; EPDSetPosition(pOBD, 0,0,pOBD->native_width, pOBD->native_height); if (pOBD->type == EPD579_792x272) { EPDFill(pOBD, ucRAM1, ucPattern1); EPDFill(pOBD, ucRAM2, ~ucPattern1); EPDFill(pOBD, ucRAM1 | 0x80, ucPattern1); EPDFill(pOBD, ucRAM2 | 0x80, ~ucPattern1); } else { EPDFill(pOBD, ucRAM1, ucPattern1); EPDFill(pOBD, ucRAM2, ucPattern2); } pOBD->iOrientation = iOldRotation; } #endif // !MEMORY_ONLY return; } if (pOBD->iOrientation == 0 || pOBD->iOrientation == 180) { iLines = (pOBD->height+7) >> 3; iCols = pOBD->width; } else { // rotated iLines = (pOBD->width+7) >> 3; iCols = pOBD->height; } memset(u8Cache, ucData, iCols); if (bRender) { // write to the physical display if render = true for (y=0; yucScreen) { memset(pOBD->ucScreen, ucData, (pOBD->width * pOBD->height)/8); } } /* obdFill() */ // // Provide or revoke a back buffer for your OLED graphics // This allows you to manage the RAM used by ss_oled on tiny // embedded platforms like the ATmega series // Pass NULL to revoke the buffer. Make sure you provide a buffer // large enough for your display (e.g. 128x64 needs 1K - 1024 bytes) // void obdSetBackBuffer(OBDISP *pOBD, uint8_t *pBuffer) { if (pOBD == NULL || pBuffer == NULL) return; pOBD->ucScreen = pBuffer; pOBD->iScreenOffset = 0; if (pOBD->type >= LCD_COUNT) // invalid type, set to command output pOBD->type = DISPLAY_COMMANDS; } /* obdSetBackBuffer() */ void obdAllocBuffer(OBDISP *pOBD) { pOBD->ucScreen = (uint8_t *)malloc(pOBD->width * ((pOBD->height+7)>>3)); pOBD->iScreenOffset = 0; } /* obdAllocBuffer() */ void obdDrawLine(OBDISP *pOBD, int x1, int y1, int x2, int y2, uint8_t ucColor, int bRender) { int temp; int dx = x2 - x1; int dy = y2 - y1; int error; uint8_t *p, *pStart, ucFill = 0, mask, bOld, bNew; int xinc, yinc; int y, x; int iPitch = pOBD->width; int iRedOffset = 0; if (pOBD == NULL) return; if (pOBD->type == DISPLAY_COMMANDS) { // encode this as a command sequence obdWriteCmdByte(pOBD, OBD_DRAWLINE | ((ucColor & 1) << 4)); obdWriteCmdInt(pOBD, x1); obdWriteCmdInt(pOBD, y1); obdWriteCmdInt(pOBD, x2); obdWriteCmdInt(pOBD, y2); return; // done } if (x1 < 0 || x2 < 0 || y1 < 0 || y2 < 0 || x1 >= pOBD->width || x2 >= pOBD->width || y1 >= pOBD->height || y2 >= pOBD->height) return; if (ucColor == OBD_RED && pOBD->iFlags & OBD_3COLOR) { // use the second half of the image buffer iRedOffset = pOBD->width * ((pOBD->height+7)/8); } if (!pOBD->ucScreen) { // no back buffer, draw in local buffer bRender = 1; // make sure it gets transmitted if (pOBD->type >= EPD42_400x300) { // no back buffer on EPD means we may need to invert the color if (ucColor == OBD_RED) ucColor = OBD_BLACK; else ucColor = 1-ucColor; // swap black/white } if (ucColor == OBD_BLACK) // fill with opposite color ucFill = 0; else ucFill = 0xff; memset(u8Cache, ucFill, sizeof(u8Cache)); } if(abs(dx) > abs(dy)) { // X major case if(x2 < x1) { dx = -dx; temp = x1; x1 = x2; x2 = temp; temp = y1; y1 = y2; y2 = temp; } y = y1; dy = (y2 - y1); error = dx >> 1; yinc = 1; if (dy < 0) { dy = -dy; yinc = -1; } if (pOBD->ucScreen) { p = pStart = &pOBD->ucScreen[iRedOffset + x1 + ((y >> 3) * iPitch)]; // point to current spot in back buffer } else { // no back buffer, draw directly to the display RAM p = pStart = u8Cache; } mask = 1 << (y & 7); // current bit offset for(x=x1; x1 <= x2; x1++) { if (ucColor == OBD_BLACK) *p++ |= mask; // set pixel and increment x pointer else *p++ &= ~mask; error -= dy; if (error < 0) { error += dx; if (yinc > 0) mask <<= 1; else mask >>= 1; if (mask == 0) // we've moved outside the current row, write the data we changed { if (bRender) { obdSetPosition(pOBD, x, y, bRender); obdWriteDataBlock(pOBD, pStart, (int)(p-pStart), bRender); // write the row we changed } x = x1+1; // we've already written the byte at x1 y1 = y+yinc; if (pOBD->ucScreen) { p += (yinc > 0) ? iPitch : -iPitch; pStart = p; } else { // no buffer memset(pStart, ucFill, (int)(p-pStart)); p = pStart; } mask = 1 << (y1 & 7); } y += yinc; } } // for x1 if (p != pStart && bRender) // some data needs to be written { obdSetPosition(pOBD, x, y, bRender); obdWriteDataBlock(pOBD, pStart, (int)(p-pStart), bRender); } } else { // Y major case if(y1 > y2) { dy = -dy; temp = x1; x1 = x2; x2 = temp; temp = y1; y1 = y2; y2 = temp; } if (pOBD->ucScreen) { p = &pOBD->ucScreen[iRedOffset + x1 + ((y1 >> 3) * iPitch)]; // point to current spot in back buffer bOld = bNew = p[0]; // current pixels } else { p = u8Cache; bOld = bNew = ucFill; } mask = 1 << (y1 & 7); // current bit offset dx = (x2 - x1); error = dy >> 1; xinc = 1; if (dx < 0) { dx = -dx; xinc = -1; } for(x = x1; y1 <= y2; y1++) { if (ucColor == OBD_BLACK) bNew |= mask; // set the pixel else bNew &= ~mask; error -= dx; mask <<= 1; // y1++ if (mask == 0) // we're done with this byte, write it if necessary { if (bOld != bNew) { p[0] = bNew; // save to RAM if (bRender) { obdSetPosition(pOBD, x, y1, bRender); obdWriteDataBlock(pOBD, &bNew, 1, bRender); } } // data changed if (pOBD->ucScreen) { p += iPitch; // next line bOld = bNew = p[0]; } else { bOld = bNew = ucFill; } mask = 1; // start at LSB again } if (error < 0) { error += dy; if (bOld != bNew) // write the last byte we modified if it changed { p[0] = bNew; // save to RAM if (bRender) { obdSetPosition(pOBD, x, y1, bRender); obdWriteDataBlock(pOBD, &bNew, 1, bRender); } } x += xinc; if (pOBD->ucScreen) { p += xinc; bOld = bNew = p[0]; } else { bOld = bNew = ucFill; } } } // for y if (bOld != bNew) // write the last byte we modified if it changed { p[0] = bNew; // save to RAM if (bRender) { obdSetPosition(pOBD, x, y2, bRender); obdWriteDataBlock(pOBD, &bNew, 1, bRender); } } } // y major case } /* obdDrawLine() */ // // For drawing ellipses, a circle is drawn and the x and y pixels are scaled by a 16-bit integer fraction // This function draws a single pixel and scales its position based on the x/y fraction of the ellipse // static void DrawScaledPixel(OBDISP *pOBD, int iCX, int iCY, int x, int y, int32_t iXFrac, int32_t iYFrac, uint8_t ucColor) { uint8_t *d, ucMask; int iPitch; int iRedOffset = 0; iPitch = pOBD->width; if (ucColor == OBD_RED && pOBD->iFlags & OBD_3COLOR) { // use the second half of the image buffer iRedOffset = pOBD->width * ((pOBD->height+7)/8); } if (iXFrac != 0x10000) x = ((x * iXFrac) >> 16); if (iYFrac != 0x10000) y = ((y * iYFrac) >> 16); x += iCX; y += iCY; if (x < 0 || x >= pOBD->width || y < 0 || y >= pOBD->height) return; // off the screen d = &pOBD->ucScreen[iRedOffset + ((y >> 3)*iPitch) + x]; ucMask = 1 << (y & 7); if (ucColor) *d |= ucMask; else *d &= ~ucMask; } /* DrawScaledPixel() */ // // For drawing filled ellipses // static void DrawScaledLine(OBDISP *pOBD, int iCX, int iCY, int x, int y, int32_t iXFrac, int32_t iYFrac, uint8_t ucColor) { int iLen, x2; uint8_t *d, ucMask; int iPitch; int iRedOffset = 0; if (ucColor == OBD_RED && pOBD->iFlags & OBD_3COLOR) { // use the second half of the image buffer iRedOffset = pOBD->width * ((pOBD->height+7)/8); } iPitch = pOBD->width; if (iXFrac != 0x10000) x = ((x * iXFrac) >> 16); if (iYFrac != 0x10000) y = ((y * iYFrac) >> 16); iLen = x*2; x = iCX - x; y += iCY; x2 = x + iLen; if (y < 0 || y >= pOBD->height) return; // completely off the screen if (x < 0) x = 0; if (x2 >= pOBD->width) x2 = pOBD->width-1; iLen = x2 - x + 1; // new length d = &pOBD->ucScreen[iRedOffset + ((y >> 3)*iPitch) + x]; ucMask = 1 << (y & 7); if (ucColor) // white { for (; iLen > 0; iLen--) *d++ |= ucMask; } else // black { for (; iLen > 0; iLen--) *d++ &= ~ucMask; } } /* DrawScaledLine() */ // // Draw the 8 pixels around the Bresenham circle // (scaled to make an ellipse) // static void BresenhamCircle(OBDISP *pOBD, int iCX, int iCY, int x, int y, int32_t iXFrac, int32_t iYFrac, uint8_t ucColor, uint8_t bFill) { if (bFill) // draw a filled ellipse { // for a filled ellipse, draw 4 lines instead of 8 pixels DrawScaledLine(pOBD, iCX, iCY, x, y, iXFrac, iYFrac, ucColor); DrawScaledLine(pOBD, iCX, iCY, x, -y, iXFrac, iYFrac, ucColor); DrawScaledLine(pOBD, iCX, iCY, y, x, iXFrac, iYFrac, ucColor); DrawScaledLine(pOBD, iCX, iCY, y, -x, iXFrac, iYFrac, ucColor); } else // draw 8 pixels around the edges { DrawScaledPixel(pOBD, iCX, iCY, x, y, iXFrac, iYFrac, ucColor); DrawScaledPixel(pOBD, iCX, iCY, -x, y, iXFrac, iYFrac, ucColor); DrawScaledPixel(pOBD, iCX, iCY, x, -y, iXFrac, iYFrac, ucColor); DrawScaledPixel(pOBD, iCX, iCY, -x, -y, iXFrac, iYFrac, ucColor); DrawScaledPixel(pOBD, iCX, iCY, y, x, iXFrac, iYFrac, ucColor); DrawScaledPixel(pOBD, iCX, iCY, -y, x, iXFrac, iYFrac, ucColor); DrawScaledPixel(pOBD, iCX, iCY, y, -x, iXFrac, iYFrac, ucColor); DrawScaledPixel(pOBD, iCX, iCY, -y, -x, iXFrac, iYFrac, ucColor); } } /* BresenhamCircle() */ // // Draw an outline or filled ellipse // void obdEllipse(OBDISP *pOBD, int iCenterX, int iCenterY, int32_t iRadiusX, int32_t iRadiusY, uint8_t ucColor, uint8_t bFilled) { int32_t iXFrac, iYFrac; int iRadius, iDelta, x, y; if (pOBD == NULL || pOBD->ucScreen == NULL) return; // must have back buffer defined if (pOBD->type == DISPLAY_COMMANDS) { // encode this as a command sequence obdWriteCmdByte(pOBD, OBD_DRAWELLIPSE | ((ucColor & 1) << 4) | ((bFilled & 1) << 5)); obdWriteCmdInt(pOBD, iCenterX); obdWriteCmdInt(pOBD, iCenterY); obdWriteCmdInt(pOBD, iRadiusX); obdWriteCmdInt(pOBD, iRadiusY); return; // done } if (iRadiusX <= 0 || iRadiusY <= 0) return; // invalid radii if (iRadiusX > iRadiusY) // use X as the primary radius { iRadius = iRadiusX; iXFrac = 65536; iYFrac = (iRadiusY * 65536) / iRadiusX; } else { iRadius = iRadiusY; iXFrac = (iRadiusX * 65536) / iRadiusY; iYFrac = 65536; } iDelta = 3 - (2 * iRadius); x = 0; y = iRadius; while (x <= y) { BresenhamCircle(pOBD, iCenterX, iCenterY, x, y, iXFrac, iYFrac, ucColor, bFilled); x++; if (iDelta < 0) { iDelta += (4*x) + 6; } else { iDelta += 4 * (x-y) + 10; y--; } } } /* obdEllipse() */ // // Draw an outline or filled rectangle // void obdRectangle(OBDISP *pOBD, int x1, int y1, int x2, int y2, uint8_t ucColor, uint8_t bFilled) { uint8_t *d, ucMask, ucMask2; int tmp, iOff; int iPitch; int iRedOffset = 0; if (ucColor == OBD_RED) { if (pOBD->iFlags & OBD_3COLOR) { // use the second half of the image buffer iRedOffset = pOBD->width * ((pOBD->height+7)/8); } else { // force red to black if not present ucColor = OBD_BLACK; } } if (pOBD == NULL || pOBD->ucScreen == NULL) return; // only works with a back buffer if (pOBD->type == DISPLAY_COMMANDS) { // encode this as a command sequence obdWriteCmdByte(pOBD, OBD_DRAWRECT | ((ucColor & 1) << 4) | ((bFilled & 1) << 5)); obdWriteCmdInt(pOBD, x1); obdWriteCmdInt(pOBD, y1); obdWriteCmdInt(pOBD, x2); obdWriteCmdInt(pOBD, y2); return; // done } if (x1 < 0 || y1 < 0 || x2 < 0 || y2 < 0 || x1 >= pOBD->width || y1 >= pOBD->height || x2 >= pOBD->width || y2 >= pOBD->height) return; // invalid coordinates iPitch = pOBD->width; // Make sure that X1/Y1 is above and to the left of X2/Y2 // swap coordinates as needed to make this true if (x2 < x1) { tmp = x1; x1 = x2; x2 = tmp; } if (y2 < y1) { tmp = y1; y1 = y2; y2 = tmp; } if (bFilled) { int x, y, iMiddle; iMiddle = (y2 >> 3) - (y1 >> 3); ucMask = 0xff << (y1 & 7); if (iMiddle == 0) // top and bottom lines are in the same row ucMask &= (0xff >> (7-(y2 & 7))); d = &pOBD->ucScreen[iRedOffset + (y1 >> 3)*iPitch + x1]; // Draw top for (x = x1; x <= x2; x++) { if (ucColor) *d |= ucMask; else *d &= ~ucMask; d++; } if (iMiddle > 1) // need to draw middle part { ucMask = (ucColor) ? 0xff : 0x00; for (y=1; yucScreen[iRedOffset + (y1 >> 3)*iPitch + x1 + (y*iPitch)]; for (x = x1; x <= x2; x++) *d++ = ucMask; } } if (iMiddle >= 1) // need to draw bottom part { ucMask = 0xff >> (7-(y2 & 7)); d = &pOBD->ucScreen[iRedOffset + (y2 >> 3)*iPitch + x1]; for (x = x1; x <= x2; x++) { if (ucColor) *d++ |= ucMask; else *d++ &= ~ucMask; } } } else // outline { // see if top and bottom lines are within the same byte rows d = &pOBD->ucScreen[iRedOffset + (y1 >> 3)*iPitch + x1]; if ((y1 >> 3) == (y2 >> 3)) { ucMask2 = 0xff << (y1 & 7); // L/R end masks ucMask = 1 << (y1 & 7); ucMask |= 1 << (y2 & 7); ucMask2 &= (0xff >> (7-(y2 & 7))); if (ucColor) { *d++ |= ucMask2; // start x1++; for (; x1 < x2; x1++) *d++ |= ucMask; if (x1 <= x2) *d++ |= ucMask2; // right edge } else { *d++ &= ~ucMask2; x1++; for (; x1 < x2; x1++) *d++ &= ~ucMask; if (x1 <= x2) *d++ &= ~ucMask2; // right edge } } else { int y; // L/R sides iOff = (x2 - x1); ucMask = 1 << (y1 & 7); for (y=y1; y <= y2; y++) { if (ucColor) { *d |= ucMask; d[iOff] |= ucMask; } else { *d &= ~ucMask; d[iOff] &= ~ucMask; } ucMask <<= 1; if (ucMask == 0) { ucMask = 1; d += iPitch; } } // T/B sides ucMask = 1 << (y1 & 7); ucMask2 = 1 << (y2 & 7); x1++; d = &pOBD->ucScreen[iRedOffset + (y1 >> 3)*iPitch + x1]; iOff = (y2 >> 3) - (y1 >> 3); iOff *= iPitch; for (; x1 < x2; x1++) { if (ucColor) { *d |= ucMask; d[iOff] |= ucMask2; } else { *d &= ~ucMask; d[iOff] &= ~ucMask2; } d++; } } } // outline } /* obdRectangle() */ // // Copy the current bitmap buffer from its native form (LSB_FIRST, VERTICAL_BYTES) to the requested form // returns 0 for success, -1 for error // Constants to be combined for the iFlags parameter: // Output format options - // OBD_LSB_FIRST 0x001 // OBD_MSB_FIRST 0x002 // OBD_VERT_BYTES 0x004 // OBD_HORZ_BYTES 0x008 // Orientation Options - // OBD_ROTATE_90 0x010 // OBD_FLIP_VERT 0x020 // OBD_FLIP_HORZ 0x040 // Polarity Options - // OBD_INVERT 0x080 int obdCopy(OBDISP *pOBD, int iFlags, uint8_t *pDestination) { int i, x, y, iPitch, iSize; int xStart, xEnd, yStart, yEnd, yDst, xDst, dx, dy; uint8_t ucSrcMask, ucDstMask, *s, *d; iPitch = pOBD->width; if (pDestination == NULL || pOBD == NULL || pOBD->ucScreen == NULL) return OBD_ERROR_BAD_PARAMETER; // calculate output buffer size if (iFlags & OBD_HORZ_BYTES) { if (iFlags & OBD_ROTATE_90) iSize = ((pOBD->height + 7)>>3) * pOBD->width; else iSize = ((pOBD->width + 7)>>3) * pOBD->height; } else { if (iFlags & OBD_ROTATE_90) iSize = (pOBD->height * ((pOBD->width+7)>>3)); else iSize = (pOBD->width * ((pOBD->height+7)>>3)); } memset(pDestination, 0, iSize); // start with 0 in dest // Prepare vars for walking through the source image if (iFlags & OBD_ROTATE_90) { if (iFlags & OBD_FLIP_HORZ) { dy = 1; yStart = 0; yEnd = pOBD->height; } else { dy = -1; yStart = pOBD->height-1; yEnd = -1; } if (iFlags & OBD_FLIP_VERT) { dx = -1; xStart = pOBD->width-1; xEnd = -1; } else { dx = 1; xStart = 0; xEnd = pOBD->width; } } else { // not rotated if (iFlags & OBD_FLIP_HORZ) { dx = -1; xStart = pOBD->width-1; xEnd = -1; } else { dx = 1; xStart = 0; xEnd = pOBD->width; } if (iFlags & OBD_FLIP_VERT) { dy = -1; yStart = pOBD->height-1; yEnd = -1; } else { dy = 1; yStart = 0; yEnd = pOBD->height; } } // Due to the possible number of permutations, the different loops // are more generic and handle flips/bit-direction with a more general // approach which moves individual pixels even when a more efficient // method is possible. More cycles, but able to do EVERYTHING // Separate output by byte orientation // Vertical bytes here if (iFlags & OBD_VERT_BYTES) { if (iFlags & OBD_ROTATE_90) { xDst = 0; for (y=yStart; y!=yEnd; y += dy, xDst++) { ucSrcMask = (1 << (y & 7)); yDst = 0; s = &pOBD->ucScreen[(y >> 3) * iPitch]; d = &pDestination[xDst]; for (x=xStart; x!=xEnd; x += dx, yDst++) { if (s[x] & ucSrcMask) {// set pixel, copy to dest if (iFlags & OBD_LSB_FIRST) d[(yDst >> 3)*pOBD->height] |= (1 << (yDst & 7)); else d[(yDst >> 3)*pOBD->height] |= (0x80 >> (yDst & 7)); } } // for x } // for y } // rotate 90 else // normally oriented { yDst = 0; for (y=yStart; y!=yEnd; y += dy, yDst++) { ucSrcMask = (1 << (y & 7)); if (iFlags & OBD_LSB_FIRST) ucDstMask = (1 << (y & 7)); else ucDstMask = (0x80 >> (y & 7)); xDst = 0; s = &pOBD->ucScreen[(y >> 3) * iPitch]; d = &pDestination[(yDst >> 3) * iPitch]; for (x=xStart; x!=xEnd; x += dx, xDst++) { if (s[x] & ucSrcMask) // set pixel, copy to dest d[xDst] |= ucDstMask; } // for x } // for y } // normal orientation } // vertical bytes else // Horizontal bytes here { if (iFlags & OBD_ROTATE_90) { int iDstPitch = (pOBD->height + 7)>>3; // dest bytes per line xDst = 0; for (y=yStart; y!=yEnd; y += dy, xDst++) { ucSrcMask = (1 << (y & 7)); yDst = 0; s = &pOBD->ucScreen[(y >> 3) * iPitch]; d = &pDestination[xDst >> 3]; ucDstMask = (iFlags & OBD_LSB_FIRST) ? (1 << (xDst & 7)) : (0x80 >> (xDst & 7)); for (x=xStart; x!=xEnd; x += dx, yDst++) { if (s[x] & ucSrcMask) // set pixel, copy to dest d[yDst * iDstPitch] |= ucDstMask; } // for x } // for y } // rotate 90 else // normally oriented { int iDstPitch = (pOBD->width + 7)>>3; // dest bytes per line yDst = 0; for (y=yStart; y!=yEnd; y += dy, yDst++) { ucSrcMask = (1 << (y & 7)); xDst = 0; s = &pOBD->ucScreen[(y >> 3) * iPitch]; d = &pDestination[yDst * iDstPitch]; ucDstMask = (iFlags & OBD_LSB_FIRST) ? 0x1 : 0x80; for (x=xStart; x!=xEnd; x += dx, xDst++) { if (s[x] & ucSrcMask) // set pixel, copy to dest d[(xDst>>3)] |= ucDstMask; if (iFlags & OBD_LSB_FIRST) ucDstMask <<= 1; else ucDstMask >>= 1; if (ucDstMask == 0) ucDstMask = (iFlags & OBD_LSB_FIRST) ? 0x1 : 0x80; } // for x } // for y } // normal orientation } // Invert all pixels? if (iFlags & OBD_INVERT) { for (i=0; i