mirror of
https://github.com/taigrr/arduinolibs
synced 2025-01-18 04:33:12 -08:00
957 lines
28 KiB
C++
957 lines
28 KiB
C++
/*
|
|
* Copyright (C) 2012 Southern Storm Software, Pty Ltd.
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a
|
|
* copy of this software and associated documentation files (the "Software"),
|
|
* to deal in the Software without restriction, including without limitation
|
|
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
* and/or sell copies of the Software, and to permit persons to whom the
|
|
* Software is furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included
|
|
* in all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
|
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
* DEALINGS IN THE SOFTWARE.
|
|
*/
|
|
|
|
#include "Bitmap.h"
|
|
#include <WString.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
|
|
/**
|
|
* \class Bitmap Bitmap.h <Bitmap.h>
|
|
* \brief Represents a monochrome bitmap within main memory.
|
|
*
|
|
* Bitmaps are a rectangular arrangement of width() x height() pixels,
|
|
* with each pixel set to either \ref Black or \ref White. The co-ordinate
|
|
* system has origin (0, 0) at the top-left of the bitmap.
|
|
*
|
|
* Functions within this class can be used to draw various shapes into
|
|
* the bitmap's data() buffer; e.g. drawLine(), drawRect(), drawBitmap(),
|
|
* drawText(), clear(), fill(), etc.
|
|
*
|
|
* \sa DMD
|
|
*/
|
|
|
|
/**
|
|
* \typedef Bitmap::Color
|
|
* \brief Type that represents the color of a pixel in a bitmap.
|
|
*
|
|
* \sa Black, White
|
|
*/
|
|
|
|
/**
|
|
* \var Bitmap::Black
|
|
* \brief Color value corresponding to "black".
|
|
*/
|
|
|
|
/**
|
|
* \var Bitmap::White
|
|
* \brief Color value corresponding to "white". If the bitmap is
|
|
* displayed on a LED array, then it may have a different physical color.
|
|
*
|
|
* Note: while the value of this constant is 1, the bitmap itself stores
|
|
* white pixels as 0 and black as 1 because the DMD display uses 1 to
|
|
* indicate a pixel being off.
|
|
*/
|
|
|
|
/**
|
|
* \var Bitmap::NoFill
|
|
* \brief Special color value that is used with drawRect() and drawCircle()
|
|
* to indicate that the interior of the shape should not be filled.
|
|
* For all other uses, \ref NoFill is equivalent to \ref White.
|
|
*/
|
|
|
|
/**
|
|
* \brief Constructs a new in-memory bitmap that is \a width x \a height
|
|
* pixels in size.
|
|
*
|
|
* \sa width(), height(), isValid()
|
|
*/
|
|
Bitmap::Bitmap(int width, int height)
|
|
: _width(width)
|
|
, _height(height)
|
|
, _stride((width + 7) / 8)
|
|
, fb(0)
|
|
, _font(0)
|
|
, _textColor(White)
|
|
{
|
|
// Allocate memory for the framebuffer and clear it (1 = pixel off).
|
|
unsigned int size = _stride * _height;
|
|
fb = (uint8_t *)malloc(size);
|
|
if (fb)
|
|
memset(fb, 0xFF, size);
|
|
}
|
|
|
|
/**
|
|
* \brief Destroys this bitmap.
|
|
*/
|
|
Bitmap::~Bitmap()
|
|
{
|
|
if (fb)
|
|
free(fb);
|
|
}
|
|
|
|
/**
|
|
* \fn bool Bitmap::isValid() const
|
|
* \brief Returns true if the memory for this bitmap is valid; false otherwise.
|
|
*
|
|
* This function can be called just after the constructor to determine if
|
|
* the memory for the bitmap was allocated successfully.
|
|
*/
|
|
|
|
/**
|
|
* \fn int Bitmap::width() const
|
|
* \brief Returns the width of the bitmap in pixels.
|
|
*
|
|
* \sa height(), stride(), bitsPerPixel()
|
|
*/
|
|
|
|
/**
|
|
* \fn int Bitmap::height() const
|
|
* \brief Returns the height of the bitmap in pixels.
|
|
*
|
|
* \sa width(), bitsPerPixel()
|
|
*/
|
|
|
|
/**
|
|
* \fn int Bitmap::stride() const
|
|
* \brief Returns the number of bytes in each line of the bitmap's data() buffer.
|
|
*
|
|
* \sa width(), bitsPerPixel(), data()
|
|
*/
|
|
|
|
/**
|
|
* \fn int Bitmap::bitsPerPixel() const
|
|
* \brief Returns the number of bits per pixel for the bitmap; always 1.
|
|
*
|
|
* \sa width(), height()
|
|
*/
|
|
|
|
/**
|
|
* \fn uint8_t *Bitmap::data()
|
|
* \brief Returns a pointer to the start of the bitmap's data buffer.
|
|
*
|
|
* The data is organized as height() lines of stride() bytes, laid out
|
|
* horizontally across the extent of width() pixels. The most significant
|
|
* bit in each byte has the lowest x value.
|
|
*
|
|
* Note: bits within the data are 1 for \ref Black and 0 for \ref White,
|
|
* which is the reverse of the constant values. This differs from pixel()
|
|
* which returns the correct constant.
|
|
*
|
|
* \sa pixel(), stride()
|
|
*/
|
|
|
|
/**
|
|
* \fn const uint8_t *Bitmap::data() const
|
|
* \brief Returns a constant pointer to the start of the bitmap's data buffer.
|
|
* \overload
|
|
*/
|
|
|
|
/**
|
|
* \brief Clears the entire bitmap to the specified \a color.
|
|
*
|
|
* \sa fill()
|
|
*/
|
|
void Bitmap::clear(Color color)
|
|
{
|
|
unsigned int size = _stride * _height;
|
|
if (color == Black)
|
|
memset(fb, 0xFF, size);
|
|
else
|
|
memset(fb, 0x00, size);
|
|
}
|
|
|
|
/**
|
|
* \brief Returns the color of the pixel at (\a x, \a y); either \ref Black
|
|
* or \ref White.
|
|
*
|
|
* Returns \a Black if \a x or \a y is out of range.
|
|
*
|
|
* \sa setPixel(), data()
|
|
*/
|
|
Bitmap::Color Bitmap::pixel(int x, int y) const
|
|
{
|
|
if (((unsigned int)x) >= ((unsigned int)_width) ||
|
|
((unsigned int)y) >= ((unsigned int)_height))
|
|
return Black;
|
|
uint8_t *ptr = fb + y * _stride + (x >> 3);
|
|
if (*ptr & ((uint8_t)0x80) >> (x & 0x07))
|
|
return Black;
|
|
else
|
|
return White;
|
|
}
|
|
|
|
/**
|
|
* \brief Sets the pixel at (\a x, \a y) to \a color.
|
|
*
|
|
* \sa pixel()
|
|
*/
|
|
void Bitmap::setPixel(int x, int y, Color color)
|
|
{
|
|
if (((unsigned int)x) >= ((unsigned int)_width) ||
|
|
((unsigned int)y) >= ((unsigned int)_height))
|
|
return; // Pixel is off-screen.
|
|
uint8_t *ptr = fb + y * _stride + (x >> 3);
|
|
if (color)
|
|
*ptr &= ~(((uint8_t)0x80) >> (x & 0x07));
|
|
else
|
|
*ptr |= (((uint8_t)0x80) >> (x & 0x07));
|
|
}
|
|
|
|
/**
|
|
* \brief Draws a line from (\a x1, \a y1) to (\a x2, \a y2) in \a color.
|
|
*
|
|
* \sa drawRect(), drawCircle()
|
|
*/
|
|
void Bitmap::drawLine(int x1, int y1, int x2, int y2, Color color)
|
|
{
|
|
// Midpoint line scan-conversion algorithm from "Computer Graphics:
|
|
// Principles and Practice", Second Edition, Foley, van Dam, et al.
|
|
int dx = x2 - x1;
|
|
int dy = y2 - y1;
|
|
int xstep, ystep;
|
|
int d, incrE, incrNE;
|
|
if (dx < 0) {
|
|
xstep = -1;
|
|
dx = -dx;
|
|
} else {
|
|
xstep = 1;
|
|
}
|
|
if (dy < 0) {
|
|
ystep = -1;
|
|
dy = -dy;
|
|
} else {
|
|
ystep = 1;
|
|
}
|
|
if (dx >= dy) {
|
|
d = 2 * dy - dx;
|
|
incrE = 2 * dy;
|
|
incrNE = 2 * (dy - dx);
|
|
setPixel(x1, y1, color);
|
|
while (x1 != x2) {
|
|
if (d <= 0) {
|
|
d += incrE;
|
|
} else {
|
|
d += incrNE;
|
|
y1 += ystep;
|
|
}
|
|
x1 += xstep;
|
|
setPixel(x1, y1, color);
|
|
}
|
|
} else {
|
|
d = 2 * dx - dy;
|
|
incrE = 2 * dx;
|
|
incrNE = 2 * (dx - dy);
|
|
setPixel(x1, y1, color);
|
|
while (y1 != y2) {
|
|
if (d <= 0) {
|
|
d += incrE;
|
|
} else {
|
|
d += incrNE;
|
|
x1 += xstep;
|
|
}
|
|
y1 += ystep;
|
|
setPixel(x1, y1, color);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* \brief Draws a rectangle from (\a x1, \a y1) to (\a x2, \a y2), with the
|
|
* outline in \a borderColor and the interior filled with \a fillColor.
|
|
*
|
|
* If \a fillColor is \ref NoFill, then the interior is not filled.
|
|
*
|
|
* \sa drawFilledRect(), drawLine(), drawCircle(), fill()
|
|
*/
|
|
void Bitmap::drawRect(int x1, int y1, int x2, int y2, Color borderColor, Color fillColor)
|
|
{
|
|
int temp;
|
|
if (x1 > x2) {
|
|
temp = x1;
|
|
x1 = x2;
|
|
x2 = temp;
|
|
}
|
|
if (y1 > y2) {
|
|
temp = y1;
|
|
y1 = y2;
|
|
y2 = temp;
|
|
}
|
|
if (fillColor == borderColor) {
|
|
fill(x1, y1, x2 - x1 + 1, y2 - y1 + 1, fillColor);
|
|
} else {
|
|
drawLine(x1, y1, x2, y1, borderColor);
|
|
if (y1 < y2)
|
|
drawLine(x2, y1 + 1, x2, y2, borderColor);
|
|
if (x1 < x2)
|
|
drawLine(x2 - 1, y2, x1, y2, borderColor);
|
|
if (y1 < (y2 - 1))
|
|
drawLine(x1, y2 - 1, x1, y1 + 1, borderColor);
|
|
if (fillColor != NoFill)
|
|
fill(x1 + 1, y1 + 1, x2 - x1 - 1, y2 - y1 - 1, fillColor);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* \fn void Bitmap::drawFilledRect(int x1, int y1, int x2, int y2, Color color)
|
|
* \brief Draws a filled rectangle from (\a x1, \a y1) to (\a x2, \a y2)
|
|
* in \a color.
|
|
*
|
|
* This is a convenience function that is equivalent to
|
|
* drawRect(\a x1, \a y1, \a x2, \a y2, \a color, \a color).
|
|
*
|
|
* \sa drawRect(), drawFilledCircle()
|
|
*/
|
|
|
|
/**
|
|
* \brief Draws a circle with a specific center (\a centerX, \a centerY)
|
|
* and \a radius, with the outline in \a borderColor and the interior
|
|
* filled with \a fillColor.
|
|
*
|
|
* If \a fillColor is \ref NoFill, then the interior is not filled.
|
|
*
|
|
* \sa drawFilledCircle(), drawLine(), drawRect()
|
|
*/
|
|
void Bitmap::drawCircle(int centerX, int centerY, int radius, Color borderColor, Color fillColor)
|
|
{
|
|
// Midpoint circle scan-conversion algorithm using second-order
|
|
// differences from "Computer Graphics: Principles and Practice",
|
|
// Second Edition, Foley, van Dam, et al.
|
|
if (radius < 0)
|
|
radius = -radius;
|
|
int x = 0;
|
|
int y = radius;
|
|
int d = 1 - radius;
|
|
int deltaE = 3;
|
|
int deltaSE = 5 - 2 * radius;
|
|
drawCirclePoints(centerX, centerY, radius, x, y, borderColor, fillColor);
|
|
while (y > x) {
|
|
if (d < 0) {
|
|
d += deltaE;
|
|
deltaE += 2;
|
|
deltaSE += 2;
|
|
} else {
|
|
d += deltaSE;
|
|
deltaE += 2;
|
|
deltaSE += 4;
|
|
--y;
|
|
}
|
|
++x;
|
|
drawCirclePoints(centerX, centerY, radius, x, y, borderColor, fillColor);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* \fn void Bitmap::drawFilledCircle(int centerX, int centerY, int radius, Color color)
|
|
* \brief Draws a filled circle with a specific center (\a centerX, \a centerY)
|
|
* and \a radius in \a color.
|
|
*
|
|
* This is a convenience function that is equivalent to
|
|
* drawCircle(\a centerX, \a centerY, \a radius, \a color, \a color).
|
|
*
|
|
* \sa drawCircle(), drawFilledRect()
|
|
*/
|
|
|
|
/**
|
|
* \brief Draws \a bitmap at (\a x, \a y) in \a color.
|
|
*
|
|
* Bits that are set to \ref White in the \a bitmap are drawn with \a color.
|
|
* Bits that are set to \ref Black in the \a bitmap are drawn with the
|
|
* inverse of \a color. The pixel at (\a x, \a y) will be the top-left
|
|
* corner of the drawn image.
|
|
*
|
|
* Note: \a bitmap must not be the same as this object or the behaviour will
|
|
* be undefined. To copy a region of a bitmap to elsewhere within the
|
|
* same bitmap, use copy() instead.
|
|
*
|
|
* \sa drawInvertedBitmap(), copy()
|
|
*/
|
|
void Bitmap::drawBitmap(int x, int y, const Bitmap &bitmap, Color color)
|
|
{
|
|
int w = bitmap.width();
|
|
int s = bitmap.stride();
|
|
int h = bitmap.height();
|
|
Color invColor = !color;
|
|
for (uint8_t by = 0; by < h; ++by) {
|
|
const uint8_t *line = bitmap.data() + by * s;
|
|
uint8_t mask = 0x80;
|
|
uint8_t value = *line++;
|
|
for (uint8_t bx = 0; bx < w; ++bx) {
|
|
if (value & mask)
|
|
setPixel(x + bx, y + by, invColor);
|
|
else
|
|
setPixel(x + bx, y + by, color);
|
|
mask >>= 1;
|
|
if (!mask) {
|
|
mask = 0x80;
|
|
value = *line++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* \brief Draws \a bitmap at (\a x, \a y) in \a color.
|
|
*
|
|
* The \a bitmap must point to program memory. The first two bytes are the
|
|
* width and height of the bitmap in pixels. The rest of the data contains
|
|
* the pixels for the bitmap, with lines byte-aligned.
|
|
*
|
|
* Bits that are 1 in the \a bitmap are drawn with \a color. Bits that are
|
|
* 0 in the \a bitmap are drawn with the inverse of \a color. The pixel at
|
|
* (\a x, \a y) will be the top-left corner of the drawn image.
|
|
*
|
|
* \sa drawInvertedBitmap(), fill()
|
|
*/
|
|
void Bitmap::drawBitmap(int x, int y, const prog_uint8_t *bitmap, Color color)
|
|
{
|
|
uint8_t w = pgm_read_byte(bitmap);
|
|
uint8_t s = (w + 7) >> 3;
|
|
uint8_t h = pgm_read_byte(bitmap + 1);
|
|
Color invColor = !color;
|
|
for (uint8_t by = 0; by < h; ++by) {
|
|
const prog_uint8_t *line = bitmap + 2 + by * s;
|
|
uint8_t mask = 0x80;
|
|
uint8_t value = pgm_read_byte(line);
|
|
for (uint8_t bx = 0; bx < w; ++bx) {
|
|
if (value & mask)
|
|
setPixel(x + bx, y + by, color);
|
|
else
|
|
setPixel(x + bx, y + by, invColor);
|
|
mask >>= 1;
|
|
if (!mask) {
|
|
mask = 0x80;
|
|
++line;
|
|
value = pgm_read_byte(line);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* \fn void Bitmap::drawInvertedBitmap(int x, int y, const Bitmap &bitmap)
|
|
* \brief Draws \a bitmap at (\a x, \a y) in inverted colors.
|
|
*
|
|
* This is a convenience function that is equivalent to
|
|
* drawBitmap(\a x, \a y, \a bitmap, \ref Black).
|
|
*
|
|
* \sa drawBitmap()
|
|
*/
|
|
|
|
/**
|
|
* \fn void Bitmap::drawInvertedBitmap(int x, int y, const prog_uint8_t *bitmap)
|
|
* \brief Draws \a bitmap at (\a x, \a y) in inverted colors.
|
|
*
|
|
* This is a convenience function that is equivalent to
|
|
* drawBitmap(\a x, \a y, \a bitmap, \ref Black).
|
|
*
|
|
* \sa drawBitmap()
|
|
*/
|
|
|
|
/**
|
|
* \fn const prog_uint8_t *Bitmap::font() const
|
|
* \brief Returns the currently selected font, or null if none selected.
|
|
*
|
|
* \sa setFont(), drawText(), drawChar(), charWidth()
|
|
*/
|
|
|
|
/**
|
|
* \fn void Bitmap::setFont(const prog_uint8_t *font)
|
|
* \brief Sets the \a font for use with drawText() and drawChar().
|
|
*
|
|
* \code
|
|
* #include <DejaVuSans9.h>
|
|
*
|
|
* display.setFont(DejaVuSans9);
|
|
* display.drawText(0, 0, "Hello");
|
|
* \endcode
|
|
*
|
|
* New fonts can be generated with <a href="https://code.google.com/p/glcd-arduino/downloads/detail?name=GLCDFontCreator2.zip&can=2&q=">GLCDFontCreator2</a>.
|
|
*
|
|
* \sa font(), drawText(), drawChar()
|
|
*/
|
|
|
|
/**
|
|
* \fn Color Bitmap::textColor() const
|
|
* \brief Returns the color that will be used for drawing text with
|
|
* drawText() and drawChar(). The default is \ref White.
|
|
*
|
|
* \sa setTextColor(), drawText(), drawChar()
|
|
*/
|
|
|
|
/**
|
|
* \fn void Bitmap::setTextColor(Color textColor)
|
|
* \brief Sets the \a color that will be used for drawing text with
|
|
* drawText() and drawChar().
|
|
*
|
|
* \sa textColor(), drawText(), drawChar()
|
|
*/
|
|
|
|
#define fontIsFixed(font) (pgm_read_byte((font)) == 0 && \
|
|
pgm_read_byte((font) + 1) == 0)
|
|
#define fontWidth(font) (pgm_read_byte((font) + 2))
|
|
#define fontHeight(font) (pgm_read_byte((font) + 3))
|
|
#define fontFirstChar(font) (pgm_read_byte((font) + 4))
|
|
#define fontCharCount(font) (pgm_read_byte((font) + 5))
|
|
|
|
/**
|
|
* \brief Draws the \a len characters of \a str at (\a x, \a y).
|
|
*
|
|
* If \a len is less than zero, then the actual length of \a str will be used.
|
|
*
|
|
* The position (\a x, \a y) will be the upper-left pixel of the first
|
|
* character that is drawn.
|
|
*
|
|
* \sa drawChar(), textColor(), font()
|
|
*/
|
|
void Bitmap::drawText(int x, int y, const char *str, int len)
|
|
{
|
|
if (!_font)
|
|
return;
|
|
uint8_t height = fontHeight(_font);
|
|
if (len < 0)
|
|
len = strlen(str);
|
|
while (len-- > 0) {
|
|
x += drawChar(x, y, *str++);
|
|
if (len > 0) {
|
|
fill(x, y, 1, height, !_textColor);
|
|
++x;
|
|
}
|
|
if (x >= _width)
|
|
break;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* \brief Draws \a len characters starting at \a start from \a str to the
|
|
* screen at (\a x, \a y).
|
|
*
|
|
* If \a len is less than zero, then the actual length of \a str will be used.
|
|
*
|
|
* The position (\a x, \a y) will be the upper-left pixel of the first
|
|
* character that is drawn.
|
|
*
|
|
* \sa drawChar(), textColor(), font()
|
|
*/
|
|
void Bitmap::drawText(int x, int y, const String &str, int start, int len)
|
|
{
|
|
if (!_font)
|
|
return;
|
|
uint8_t height = fontHeight(_font);
|
|
if (len < 0)
|
|
len = str.length() - start;
|
|
while (len-- > 0) {
|
|
x += drawChar(x, y, str[start++]);
|
|
if (len > 0) {
|
|
fill(x, y, 1, height, !_textColor);
|
|
++x;
|
|
}
|
|
if (x >= _width)
|
|
break;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* \brief Draws a single character \a ch at (\a x, \a y).
|
|
*
|
|
* Returns the width of the character in pixels so that higher-order functions
|
|
* like drawText() can advance \a x to the location of the next character
|
|
* to be drawn. The width does not include inter-character spacing.
|
|
*
|
|
* The position (\a x, \a y) will be the upper-left pixel of the drawn
|
|
* character.
|
|
*
|
|
* \sa drawText(), textColor(), font(), charWidth()
|
|
*/
|
|
int Bitmap::drawChar(int x, int y, char ch)
|
|
{
|
|
uint8_t height = fontHeight(_font);
|
|
if (ch == ' ') {
|
|
// Font may not have space, or it is zero-width. Calculate
|
|
// the real size and fill the space.
|
|
int spaceWidth = charWidth('n');
|
|
fill(x, y, spaceWidth, height, !_textColor);
|
|
return spaceWidth;
|
|
}
|
|
uint8_t first = fontFirstChar(_font);
|
|
uint8_t count = fontCharCount(_font);
|
|
uint8_t index = (uint8_t)ch;
|
|
if (index < first || index >= (first + count))
|
|
return 0;
|
|
index -= first;
|
|
uint8_t heightBytes = (height + 7) >> 3;;
|
|
uint8_t width;
|
|
const prog_uint8_t *image;
|
|
if (fontIsFixed(_font)) {
|
|
// Fixed-width font.
|
|
width = fontWidth(_font);
|
|
image = _font + 6 + index * heightBytes * width;
|
|
} else {
|
|
// Variable-width font.
|
|
width = pgm_read_byte(_font + 6 + index);
|
|
image = _font + 6 + count;
|
|
for (uint8_t temp = 0; temp < index; ++temp) {
|
|
// Scan through all previous characters to find the starting
|
|
// location for this one.
|
|
image += pgm_read_byte(_font + 6 + temp) * heightBytes;
|
|
}
|
|
}
|
|
if ((x + width) <= 0 || (y + height) <= 0)
|
|
return width; // Character is off the top or left of the screen.
|
|
Color invColor = !_textColor;
|
|
for (uint8_t cx = 0; cx < width; ++cx) {
|
|
for (uint8_t cy = 0; cy < heightBytes; ++cy) {
|
|
uint8_t value = pgm_read_byte(image + cy * width + cx);
|
|
int posn;
|
|
if (heightBytes > 1 && cy == (heightBytes - 1))
|
|
posn = height - 8;
|
|
else
|
|
posn = cy * 8;
|
|
for (uint8_t bit = 0; bit < 8; ++bit) {
|
|
if ((posn + bit) >= (cy * 8) && (posn + bit) <= height) {
|
|
if (value & 0x01)
|
|
setPixel(x + cx, y + posn + bit, _textColor);
|
|
else
|
|
setPixel(x + cx, y + posn + bit, invColor);
|
|
}
|
|
value >>= 1;
|
|
}
|
|
}
|
|
}
|
|
return width;
|
|
}
|
|
|
|
/**
|
|
* \brief Returns the width in pixels of \a ch in the current font().
|
|
*
|
|
* Returns zero if font() is not set, or \a ch is not present in font().
|
|
*
|
|
* \sa drawChar(), font(), textWidth(), textHeight()
|
|
*/
|
|
int Bitmap::charWidth(char ch) const
|
|
{
|
|
uint8_t index = (uint8_t)ch;
|
|
if (!_font)
|
|
return 0;
|
|
uint8_t first = fontFirstChar(_font);
|
|
uint8_t count = fontCharCount(_font);
|
|
if (index == ' ')
|
|
index = 'n'; // In case the font does not contain space.
|
|
if (index < first || index >= (first + count))
|
|
return 0;
|
|
if (fontIsFixed(_font))
|
|
return fontWidth(_font);
|
|
else
|
|
return pgm_read_byte(_font + 6 + (index - first));
|
|
}
|
|
|
|
/**
|
|
* \brief Returns the width in pixels of the \a len characters of \a str
|
|
* in the current font(), including inter-character spacing.
|
|
*
|
|
* If \a len is less than zero, then the actual length of \a str will be used.
|
|
*
|
|
* \sa drawText(), charWidth(), textHeight()
|
|
*/
|
|
int Bitmap::textWidth(const char *str, int len) const
|
|
{
|
|
int width = 0;
|
|
if (len < 0)
|
|
len = strlen(str);
|
|
while (len-- > 0) {
|
|
width += charWidth(*str++);
|
|
if (len > 0)
|
|
++width;
|
|
}
|
|
return width;
|
|
}
|
|
|
|
/**
|
|
* \brief Returns the width in pixels of the \a len characters of \a str
|
|
* in the current font(), starting at \a start, including inter-character
|
|
* spacing.
|
|
*
|
|
* If \a len is less than zero, then the actual length of \a str will be used.
|
|
*
|
|
* \sa drawText(), charWidth(), textHeight()
|
|
*/
|
|
int Bitmap::textWidth(const String &str, int start, int len) const
|
|
{
|
|
int width = 0;
|
|
if (len < 0)
|
|
len = str.length() - start;
|
|
while (len-- > 0) {
|
|
width += charWidth(str[start++]);
|
|
if (len > 0)
|
|
++width;
|
|
}
|
|
return width;
|
|
}
|
|
|
|
/**
|
|
* \brief Returns the height in pixels of the current text drawing font();
|
|
* or zero if font() is not set.
|
|
*
|
|
* \sa font(), charWidth(), textWidth()
|
|
*/
|
|
int Bitmap::textHeight() const
|
|
{
|
|
if (_font)
|
|
return fontHeight(_font);
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* \brief Copies the \a width x \a height pixels starting at top-left
|
|
* corner (\a x, \a y) to (\a destX, \a destY) in the bitmap \a dest.
|
|
*
|
|
* The \a dest bitmap can be the same as this object, in which case the copy
|
|
* will be performed in a manner that correctly handles overlapping regions.
|
|
*
|
|
* If some part of the source region is outside the bounds of this object,
|
|
* then the value \ref Black will be copied to \a dest for those pixels.
|
|
* This can be used to produce a behaviour similar to scroll() when
|
|
* \a bitmap is the same as this object.
|
|
*
|
|
* \sa drawBitmap(), fill(), scroll()
|
|
*/
|
|
void Bitmap::copy(int x, int y, int width, int height, Bitmap *dest, int destX, int destY)
|
|
{
|
|
if (dest == this) {
|
|
// Copying to within the same bitmap, so copy in a direction
|
|
// that will prevent problems with overlap.
|
|
blit(x, y, x + width - 1, y + height - 1, destX, destY);
|
|
} else {
|
|
// Copying to a different bitmap.
|
|
while (height > 0) {
|
|
for (int tempx = 0; tempx < width; ++tempx)
|
|
dest->setPixel(destX + tempx, destY, pixel(x + tempx, y));
|
|
++y;
|
|
++destY;
|
|
--height;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* \brief Fills the \a width x \a height pixels starting at top-left
|
|
* corner (\a x, \a y) with \a color.
|
|
*
|
|
* \sa copy(), clear(), invert(), drawRect()
|
|
*/
|
|
void Bitmap::fill(int x, int y, int width, int height, Color color)
|
|
{
|
|
while (height > 0) {
|
|
for (int temp = 0; temp < width; ++temp)
|
|
setPixel(x + temp, y, color);
|
|
++y;
|
|
--height;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* \brief Fills the \a width x \a height pixels starting at top-left
|
|
* corner (\a x, \a y) with the contents of \a pattern.
|
|
*
|
|
* The \a pattern must point to program memory. The first two bytes are the
|
|
* width and height of the pattern in pixels. The rest of the data contains
|
|
* the pixels for the pattern, with lines byte-aligned.
|
|
*
|
|
* Bits that are 1 in the \a pattern are drawn with \a color. Bits that are
|
|
* 0 in the \a pattern are drawn with the inverse of \a color.
|
|
*
|
|
* \sa drawBitmap(), clear(), invert()
|
|
*/
|
|
void Bitmap::fill(int x, int y, int width, int height, const prog_uint8_t *pattern, Color color)
|
|
{
|
|
uint8_t w = pgm_read_byte(pattern);
|
|
uint8_t s = (w + 7) >> 3;
|
|
uint8_t h = pgm_read_byte(pattern + 1);
|
|
if (!w || !h)
|
|
return;
|
|
Color invColor = !color;
|
|
for (int tempy = 0; tempy < height; ++tempy) {
|
|
const prog_uint8_t *startLine = pattern + 2 + (tempy % h) * s;
|
|
const prog_uint8_t *line = startLine;
|
|
uint8_t mask = 0x80;
|
|
uint8_t value = pgm_read_byte(line++);
|
|
int bit = 0;
|
|
for (int tempx = 0; tempx < width; ++tempx) {
|
|
if (value & mask)
|
|
setPixel(x + tempx, y + tempy, color);
|
|
else
|
|
setPixel(x + tempx, y + tempy, invColor);
|
|
if (++bit >= w) {
|
|
mask = 0x80;
|
|
line = startLine;
|
|
value = pgm_read_byte(line++);
|
|
bit = 0;
|
|
} else {
|
|
mask >>= 1;
|
|
if (!mask) {
|
|
mask = 0x80;
|
|
value = pgm_read_byte(line++);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* \fn void Bitmap::scroll(int dx, int dy, Color fillColor)
|
|
* \brief Scrolls the entire contents of the bitmap by \a dx and \a dy.
|
|
*
|
|
* If \a dx is 2 and \a dy is -1, then the region will be scrolled two
|
|
* pixels to the right and one pixel up. Pixels that are uncovered
|
|
* by the scroll are filled with \a fillColor.
|
|
*
|
|
* \sa copy(), fill()
|
|
*/
|
|
|
|
/**
|
|
* \brief Scrolls the \a width x \a height pixels starting at top-left
|
|
* corner (\a x, \a y) by \a dx and \a dy.
|
|
*
|
|
* If \a dx is 2 and \a dy is -1, then the region will be scrolled two
|
|
* pixels to the right and one pixel up. Pixels that are uncovered
|
|
* by the scroll are filled with \a fillColor.
|
|
*
|
|
* \sa copy(), fill()
|
|
*/
|
|
void Bitmap::scroll(int x, int y, int width, int height, int dx, int dy, Color fillColor)
|
|
{
|
|
// Bail out if no scrolling at all.
|
|
if (!dx && !dy)
|
|
return;
|
|
|
|
// Clamp the scroll region to the extents of the bitmap.
|
|
if (x < 0) {
|
|
width += x;
|
|
x = 0;
|
|
}
|
|
if (y < 0) {
|
|
height += y;
|
|
y = 0;
|
|
}
|
|
if ((x + width) > _width)
|
|
width = _width - x;
|
|
if ((y + height) > _height)
|
|
height = _height - y;
|
|
if (width <= 0 || height <= 0)
|
|
return;
|
|
|
|
// Scroll the region in the specified direction.
|
|
if (dy < 0) {
|
|
if (dx < 0)
|
|
blit(x - dx, y - dy, x + width - 1 + dx, y + height - 1 + dy, x, y);
|
|
else
|
|
blit(x, y - dy, x + width - 1 - dx, y + height - 1 + dy, x + dx, y);
|
|
} else {
|
|
if (dx < 0)
|
|
blit(x - dx, y, x + width - 1 + dx, y + height - 1 - dy, x, y + dy);
|
|
else
|
|
blit(x, y, x + width - 1 - dx, y + height - 1 - dy, x + dx, y + dy);
|
|
}
|
|
|
|
// Fill the pixels that were uncovered by the scroll.
|
|
if (dy < 0) {
|
|
fill(x, y + height + dy, width, -dy, fillColor);
|
|
if (dx < 0)
|
|
fill(x + width + dx, y, -dx, height + dy, fillColor);
|
|
else if (dx > 0)
|
|
fill(x, y, dx, height + dy, fillColor);
|
|
} else if (dy > 0) {
|
|
fill(x, y, width, -dy, fillColor);
|
|
if (dx < 0)
|
|
fill(x + width + dx, y + dy, -dx, height - dy, fillColor);
|
|
else if (dx > 0)
|
|
fill(x, y + dy, dx, height - dy, fillColor);
|
|
} else if (dx < 0) {
|
|
fill(x + width + dx, y, -dx, height, fillColor);
|
|
} else if (dx > 0) {
|
|
fill(x, y, dx, height, fillColor);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* \brief Inverts the \a width x \a height pixels starting at top-left
|
|
* corner (\a x, \a y).
|
|
*
|
|
* \sa fill()
|
|
*/
|
|
void Bitmap::invert(int x, int y, int width, int height)
|
|
{
|
|
while (height > 0) {
|
|
for (int tempx = x + width - 1; tempx >= x; --tempx)
|
|
setPixel(tempx, y, !pixel(tempx, y));
|
|
--height;
|
|
++y;
|
|
}
|
|
}
|
|
|
|
void Bitmap::blit(int x1, int y1, int x2, int y2, int x3, int y3)
|
|
{
|
|
if (y3 < y1 || (y1 == y3 && x3 <= x1)) {
|
|
for (int tempy = y1; tempy <= y2; ++tempy) {
|
|
int y = y1 - tempy + y3;
|
|
int x = x3 - x1;
|
|
for (int tempx = x1; tempx <= x2; ++tempx)
|
|
setPixel(x + tempx, y, pixel(tempx, tempy));
|
|
}
|
|
} else {
|
|
for (int tempy = y2; tempy >= y1; --tempy) {
|
|
int y = y1 - tempy + y3;
|
|
int x = x3 - x1;
|
|
for (int tempx = x2; tempx >= x1; --tempx)
|
|
setPixel(x + tempx, y, pixel(tempx, tempy));
|
|
}
|
|
}
|
|
}
|
|
|
|
void Bitmap::drawCirclePoints(int centerX, int centerY, int radius, int x, int y, Color borderColor, Color fillColor)
|
|
{
|
|
if (x != y) {
|
|
setPixel(centerX + x, centerY + y, borderColor);
|
|
setPixel(centerX + y, centerY + x, borderColor);
|
|
setPixel(centerX + y, centerY - x, borderColor);
|
|
setPixel(centerX + x, centerY - y, borderColor);
|
|
setPixel(centerX - x, centerY - y, borderColor);
|
|
setPixel(centerX - y, centerY - x, borderColor);
|
|
setPixel(centerX - y, centerY + x, borderColor);
|
|
setPixel(centerX - x, centerY + y, borderColor);
|
|
if (fillColor != NoFill) {
|
|
if (radius > 1) {
|
|
drawLine(centerX - x + 1, centerY + y, centerX + x - 1, centerY + y, fillColor);
|
|
drawLine(centerX - y + 1, centerY + x, centerX + y - 1, centerY + x, fillColor);
|
|
drawLine(centerX - x + 1, centerY - y, centerX + x - 1, centerY - y, fillColor);
|
|
drawLine(centerX - y + 1, centerY - x, centerX + y - 1, centerY - x, fillColor);
|
|
} else if (radius == 1) {
|
|
setPixel(centerX, centerY, fillColor);
|
|
}
|
|
}
|
|
} else {
|
|
setPixel(centerX + x, centerY + y, borderColor);
|
|
setPixel(centerX + y, centerY - x, borderColor);
|
|
setPixel(centerX - x, centerY - y, borderColor);
|
|
setPixel(centerX - y, centerY + x, borderColor);
|
|
if (fillColor != NoFill) {
|
|
if (radius > 1) {
|
|
drawLine(centerX - x + 1, centerY + y, centerX + x - 1, centerY + y, fillColor);
|
|
drawLine(centerX - x + 1, centerY - y, centerX + x - 1, centerY - y, fillColor);
|
|
} else if (radius == 1) {
|
|
setPixel(centerX, centerY, fillColor);
|
|
}
|
|
}
|
|
}
|
|
}
|