Escher: Introduce the TiledView

Change-Id: I97d612cf89bd9cf45f8b440881918b9626cd65f6
This commit is contained in:
Romain Goyet
2016-07-07 13:19:13 +02:00
parent 8e4b1666bb
commit 30aa62e3c5
35 changed files with 647 additions and 114 deletions

View File

@@ -1,6 +1,5 @@
#include "cursor_view.h"
void CursorView::drawRect(KDRect rect) const {
KDColor cursorColor = KDColorRed;
KDFillRect(rect, &cursorColor, 1);
KDFillRect(rect, KDColorRed);
}

View File

@@ -8,7 +8,11 @@ constexpr int kNumberOfMainGridLines = 5;
constexpr int kNumberOfSecondaryGridLines = 4;
GraphView::GraphView() :
#if GRAPH_VIEW_IS_TILED
TiledView(),
#else
View(),
#endif
m_cursorView(CursorView()),
m_cursorPosition(KDPointZero),
m_xMin(-2.0f),
@@ -39,36 +43,65 @@ void GraphView::layoutSubviews() {
m_cursorView.setFrame(cursorFrame);
}
#if GRAPH_VIEW_IS_TILED
KDColor * GraphView::tile() const {
return (KDColor *)m_tile;
}
KDSize GraphView::tileSize() const {
return {kTileWidth, kTileHeight};
}
void GraphView::drawTile(KDRect rect) const {
#else
void GraphView::drawRect(KDRect rect) const {
KDColor backgroundColor = KDColorWhite;
KDFillRect(rect, &backgroundColor, 1);
#endif
KDFillRect(rect, KDColorWhite);
drawGrid(rect);
drawAxes(rect);
drawFunction(rect);
/*
constexpr int maskLength = 3;
uint8_t mask[maskLength] = {
#if 1
0x10, 0x70, 0xE0,
#else
0x00, 0x30, 0x73, 0x30, 0x00,
0x30, 0xfb, 0xff, 0xfb, 0x30,
0x73, 0xff, 0xff, 0xff, 0x73,
0x30, 0xfb, 0xff, 0xfb, 0x30,
0x00, 0x30, 0x73, 0x30, 0x00
#endif
};
KDColor red = KDColorRed;
KDBlitRect(rect, &red, 1, mask, maskLength);
*/
}
void GraphView::drawLine(KDRect rect, Axis axis, float coordinate, KDColor color) const {
void GraphView::drawLine(KDRect rect, Axis axis, float coordinate, KDColor color, KDCoordinate thickness) const {
KDRect lineRect;
switch(axis) {
case Axis::Horizontal:
lineRect.x = rect.x;
lineRect.y = floatToPixel(Axis::Vertical, coordinate);
lineRect.width = rect.width;
lineRect.height = 1;
lineRect.height = thickness;
break;
case Axis::Vertical:
lineRect.x = floatToPixel(Axis::Horizontal, coordinate);
lineRect.y = rect.y;
lineRect.width = 1;
lineRect.width = thickness;
lineRect.height = rect.height;
break;
}
KDFillRect(lineRect, &color, 1);
KDFillRect(lineRect, color);
}
void GraphView::drawAxes(KDRect rect) const {
drawLine(rect, Axis::Horizontal, 0.0f, kAxisColor);
drawLine(rect, Axis::Vertical, 0.0f, kAxisColor);
drawLine(rect, Axis::Horizontal, 0.0f, kAxisColor, 2);
drawLine(rect, Axis::Vertical, 0.0f, kAxisColor, 2);
}
void GraphView::drawGridLines(KDRect rect, Axis axis, int count, KDColor color) const {
@@ -113,11 +146,80 @@ KDCoordinate GraphView::floatToPixel(Axis axis, float f) const {
}
void GraphView::drawFunction(KDRect rect) const {
KDPoint p = KDPointZero;
for (p.x=rect.x; p.x<(rect.x+rect.width); p.x++) {
KDPoint p;
constexpr KDCoordinate stampSize = 5;
uint8_t mask[stampSize*stampSize] = {
0x00, 0x30, 0x73, 0x30, 0x00,
0x30, 0xfb, 0xff, 0xfb, 0x30,
0x73, 0xff, 0xff, 0xff, 0x73,
0x30, 0xfb, 0xff, 0xfb, 0x30,
0x00, 0x30, 0x73, 0x30, 0x00
};
KDColor workingBuffer[stampSize*stampSize];
for (p.x=rect.x-stampSize; p.x<(rect.x+rect.width); p.x++) {
float x = pixelToFloat(Axis::Horizontal, p.x);
float y = (x-1)*(x+1)*x;
p.y = floatToPixel(Axis::Vertical, y);
KDSetPixel(p, KDColorRGB(0xCF, 0, 0));
KDRect stampRect;
stampRect.origin = p;
stampRect.width = stampSize;
stampRect.height = stampSize;
//KDColor red = KDColorRed;
KDFillRectWithMask(stampRect, KDColorRed, mask, workingBuffer);
//KDBlitRect(stampRect, &red, {1,1}, mask, {stampSize,stampSize});
}
}
#if 0
void GraphView::drawFunction(KDRect rect) const {
/* Naive algorithm: iterate on pixels horizontally
* Actually, this is kinda optimal. If two consecutive pixels don't touch
* (i.e. they have a y-difference greater than 1), just draw a vertical line.
* This may seem stupid but is kinda good actually. */
KDCoordinate previousPixelY;
KDCoordinate pixelX;
KDColor curveColor = KDColorRGB(0xFF, 0, 0);
#define ANTI_ALIASING
#ifdef ANTI_ALIASING
KDColor antiAliasingColor = KDColorRGB(0, 0xFF, 0);
#endif
for (pixelX=rect.x; pixelX<(rect.x+rect.width); pixelX++) {
float x = pixelToFloat(Axis::Horizontal, pixelX);
float y = (x-1)*(x+1)*x;
KDCoordinate pixelY = floatToPixel(Axis::Vertical, y);
KDRect r;
r.x = pixelX;
if (pixelY < previousPixelY) {
r.y = pixelY;
r.height = previousPixelY-pixelY;
KDPoint p;
p.x = pixelX-1;
p.y = previousPixelY+3;
KDSetPixel(p, KDColorRGB(0x00, 0xFF, 0x00));
p.x = pixelX;
p.y = pixelY-1;
KDSetPixel(p, KDColorRGB(0x00, 0xFF, 0x00));
} else {
r.y = previousPixelY;
r.height = pixelY - previousPixelY;
}
if (r.height == 0) {
r.height = 1;
}
r.width = 1;
r.width += 2;
r.height += 2;
//KDFillRectWithPattern(r, antialiasedDot);
KDFillRect(r, KDColorRGB(0xFF, 0, 0));
previousPixelY = pixelY;
}
}
#endif

View File

@@ -4,10 +4,27 @@
#include <escher.h>
#include "cursor_view.h"
class GraphView : public View {
#define GRAPH_VIEW_IS_TILED 1
class GraphView : public
#if GRAPH_VIEW_IS_TILED
TiledView
#else
View
#endif
{
public:
GraphView();
#if GRAPH_VIEW_IS_TILED
KDColor * tile() const override;
KDSize tileSize() const override;
void drawTile(KDRect rect) const override;
#else
void drawRect(KDRect rect) const override;
#endif
// void drawRect(KDRect rect) const override;
void moveCursorRight();
private:
int numberOfSubviews() const override;
@@ -27,13 +44,17 @@ private:
KDCoordinate floatToPixel(Axis axis, float f) const;
void drawLine(KDRect rect, Axis axis,
float coordinate, KDColor color) const;
float coordinate, KDColor color, KDCoordinate thickness = 1) const;
void drawAxes(KDRect rect) const;
void drawGrid(KDRect rect) const;
void drawGridLines(KDRect rect, Axis axis, int count, KDColor color) const;
void drawFunction(KDRect rect) const;
static constexpr KDCoordinate kTileWidth = 32;
static constexpr KDCoordinate kTileHeight = 32;
KDColor m_tile[kTileWidth*kTileHeight];
CursorView m_cursorView;
KDPoint m_cursorPosition;

View File

@@ -10,7 +10,7 @@ FunctionCell::FunctionCell() :
void FunctionCell::drawRect(KDRect rect) const {
KDColor background = m_even ? KDColorRGB(0xEE, 0xEE, 0xEE) : KDColorRGB(0x77,0x77,0x77);
KDFillRect(rect, &background, 1);
KDFillRect(rect, background);
KDDrawString(m_message, KDPointZero, m_focused);
}

View File

@@ -13,6 +13,7 @@ objs += $(addprefix escher/src/,\
table_view.o\
text_field.o\
text_view.o\
tiled_view.o\
view.o\
view_controller.o\
window.o\

View File

@@ -10,6 +10,7 @@
#include <escher/text_view.h>
#include <escher/tab_view_controller.h>
#include <escher/table_view.h>
#include <escher/tiled_view.h>
#include <escher/view.h>
#include <escher/view_controller.h>
#include <escher/window.h>

View File

@@ -0,0 +1,16 @@
#ifndef ESCHER_TILED_VIEW_H
#define ESCHER_TILED_VIEW_H
#include <escher/view.h>
class TiledView : public View {
using View::View;
protected:
void drawRect(KDRect rect) const override;
virtual void drawTile(KDRect rect) const = 0;
virtual KDColor * tile() const = 0;
virtual KDSize tileSize() const = 0;
};
#endif

View File

@@ -15,7 +15,7 @@ ScrollViewIndicator::ScrollViewIndicator(ScrollViewIndicator::Direction directio
}
void ScrollViewIndicator::drawRect(KDRect rect) const {
KDFillRect(bounds(), &k_backgroundColor, 1);
KDFillRect(bounds(), k_backgroundColor);
KDRect indicatorFrame;
if (m_direction == Direction::Horizontal) {
indicatorFrame.x = m_start*m_frame.width;
@@ -29,7 +29,7 @@ void ScrollViewIndicator::drawRect(KDRect rect) const {
indicatorFrame.width = m_frame.width;
indicatorFrame.height = (m_end-m_start)*m_frame.height;
}
KDFillRect(indicatorFrame, &k_indicatorColor, 1);
KDFillRect(indicatorFrame, k_indicatorColor);
}
void ScrollViewIndicator::setStart(float start) {

View File

@@ -7,7 +7,7 @@ SolidColorView::SolidColorView(KDColor color) :
}
void SolidColorView::drawRect(KDRect rect) const {
KDFillRect(rect, &m_color, 1);
KDFillRect(rect, m_color);
}
#if ESCHER_VIEW_LOGGING

View File

@@ -13,7 +13,7 @@ TabView::TabView() :
void TabView::drawRect(KDRect rect) const {
KDColor backgroundColor = KDColorRGB(0xb5, 0x1d, 0xab);
KDFillRect(rect, &backgroundColor, 1);
KDFillRect(rect, backgroundColor);
}
void TabView::addTabNamed(const char * name) {

53
escher/src/tiled_view.cpp Normal file
View File

@@ -0,0 +1,53 @@
#include <escher/tiled_view.h>
#include <assert.h>
static KDFrameBuffer sCurrentTile;
void tilePushRect(KDRect rect, const KDColor * pixels) {
KDFramePushRect(&sCurrentTile, rect, pixels);
}
void tilePushRectUniform(KDRect rect, KDColor color) {
KDFramePushRectUniform(&sCurrentTile, rect, color);
}
void tilePullRect(KDRect rect, KDColor * pixels) {
KDFramePullRect(&sCurrentTile, rect, pixels);
}
static KDContext sTileContext = {
{
&tilePushRect,
&tilePushRectUniform,
&tilePullRect
},
{0, 0},
{0, 0, 0, 0}
};
void TiledView::drawRect(KDRect rect) const {
sCurrentTile.pixels = tile();
sCurrentTile.size = tileSize();
sTileContext.clippingRect.origin = KDPointZero; // Useless
sTileContext.clippingRect.size = sCurrentTile.size;
KDRect tileRect;
for (int i=0; i<(rect.width/sCurrentTile.size.width+1); i++) {
for (int j=0; j<(rect.height/sCurrentTile.size.height+1); j++) {
tileRect.x = rect.x + i*sCurrentTile.size.width;
tileRect.y = rect.y + j*sCurrentTile.size.height;
tileRect.size = sCurrentTile.size;
//tileRect = KDRectIntersection(tileRect, rect); // Optional
KDContext * previousContext = KDCurrentContext;
sTileContext.origin.x = -tileRect.x;
sTileContext.origin.y = -tileRect.y;
KDCurrentContext = &sTileContext;
drawTile(tileRect);
KDCurrentContext = previousContext;
KDSize zero = {0,0};
KDFillRectWithPixels(tileRect, sCurrentTile.pixels, sCurrentTile.pixels);
}
}
}

View File

@@ -11,21 +11,16 @@
* pixel coordinates every time. We're therefore leveraging this capability
* which results in a very consequent speedup (up to ~10x faster). */
#include <stdint.h>
#include <stddef.h>
#include <kandinsky.h>
/* ION manipulates RGB565 colors */
typedef uint16_t ion_color_t;
/* Set the color of a single pixel. */
void ion_set_pixel(uint16_t x, uint16_t y, ion_color_t color);
/* Fill a rect with a single color */
void ion_fill_rect(
uint16_t x, uint16_t y,
uint16_t width, uint16_t height,
ion_color_t * pattern, size_t patternSize
);
void ion_screen_push_rect(KDRect rect, const KDColor * pixels);
void ion_screen_push_rect_uniform(KDRect rect, KDColor color);
void ion_screen_pull_rect(KDRect rect, KDColor * pixels);
/*
void ion_screen_set_working_area(KDRect area);
void ion_screen_push_pixels(const KDColor * pixels, size_t count);
void ion_screen_pull_pixels(KDColor * pixels, size_t count);
*/
#define ION_SCREEN_WIDTH 320
#define ION_SCREEN_HEIGHT 240

View File

@@ -40,25 +40,59 @@ void ion_display_off() {
// Turn off panel
}
void ion_set_pixel(uint16_t x, uint16_t y, ion_color_t color) {
st7789_set_drawing_area(&sDisplayController, x, y, 1, 1);
st7789_push_pixels(&sDisplayController, &color, 1);
void ion_screen_push_rect(KDRect rect, const KDColor * pixels) {
st7789_set_drawing_area(&sDisplayController, rect);
st7789_push_pixels(&sDisplayController, pixels, rect.width*rect.height);
}
void ion_fill_rect(
uint16_t x, uint16_t y,
uint16_t width, uint16_t height,
ion_color_t * pattern, size_t patternSize)
{
st7789_set_drawing_area(&sDisplayController, x, y, width, height);
size_t remainingSize = width*height;
while (remainingSize > 0) {
int32_t blockSize = remainingSize > patternSize ? patternSize : remainingSize;
st7789_push_pixels(&sDisplayController, pattern, blockSize);
remainingSize -= blockSize;
void ion_screen_push_rect_uniform(KDRect rect, KDColor color) {
st7789_set_drawing_area(&sDisplayController, rect);
for (size_t i=0; i<rect.width*rect.height; i++) {
st7789_push_pixels(&sDisplayController, &color, 1);
}
}
void ion_screen_pull_rect(KDRect rect, KDColor * pixels) {
assert(0); // Unimplemented
}
#if 0
void ion_screen_push_rect(
uint16_t x, uint16_t y,
uint16_t width, uint16_t height,
ion_color_t * pattern, uint16_t patternWidth, uint16_t patternHeight)
{
st7789_set_drawing_area(&sDisplayController, x, y, width, height);
#if ION_DEVICE_FILL_RECT_FAST_PATH
/* If the pattern width matches the target rect width, we can easily push
* mutliple lines at once since those will be contiguous in memory. */
if (patternWidth == width) {
size_t remainingSize = width*height;
size_t patternSize = patternWidth*patternHeight;
while (remainingSize > 0) {
int32_t blockSize = remainingSize > patternSize ? patternSize : remainingSize;
st7789_push_pixels(&sDisplayController, pattern, blockSize);
remainingSize -= blockSize;
}
return;
}
#endif
uint16_t remainingHeight = height;
uint16_t patternLine = 0;
while (remainingHeight-- > 0) {
uint16_t remainingWidth = width;
while (remainingWidth > 0) {
int32_t blockSize = remainingWidth > patternWidth ? patternWidth : remainingWidth;
st7789_push_pixels(&sDisplayController, pattern+patternLine*patternWidth, blockSize);
remainingWidth -= blockSize;
}
if (++patternLine >= patternHeight) {
patternLine = 0;
}
}
}
#endif
void init_display() {
//assert(FRAMEBUFFER_LENGTH == (FRAMEBUFFER_WIDTH*FRAMEBUFFER_HEIGHT*FRAMEBUFFER_BITS_PER_PIXEL)/8);

View File

@@ -155,11 +155,12 @@ void st7789_initialize(st7789_t * c) {
perform_instructions(c, init_sequence, sizeof(init_sequence)/sizeof(init_sequence[0]));
}
void st7789_set_drawing_area(st7789_t * controller, int16_t x, int16_t y, int16_t width, int16_t height) {
uint16_t x_start = x;
uint16_t x_end = x + width - 1;
uint16_t y_start = y;
uint16_t y_end = y + height - 1;
void st7789_set_drawing_area(st7789_t * controller, KDRect area) {
assert(sizeof(KDCoordinate) == 2); // We expect 16-bit values
uint16_t x_start = area.x;
uint16_t x_end = area.x + area.width - 1;
uint16_t y_start = area.y;
uint16_t y_end = area.y + area.height - 1;
const instruction_t sequence[] = {
COMMAND(CASET),
@@ -180,8 +181,11 @@ void st7789_set_drawing_area(st7789_t * controller, int16_t x, int16_t y, int16_
perform_instructions(controller, sequence, sizeof(sequence)/sizeof(sequence[0]));
}
void st7789_push_pixels(st7789_t * controller, ion_color_t * pixels, int32_t numberOfPixels) {
for (int32_t i=0; i<numberOfPixels; i++) {
void st7789_push_pixels(st7789_t * controller,
const KDColor * pixels, size_t numberOfPixels) {
assert(sizeof(KDColor) == 2); // We expect KDColor to be RGB565
for (size_t i=0; i<numberOfPixels; i++) {
perform_instruction(controller, DATA(pixels[i] >> 8));
perform_instruction(controller, DATA(pixels[i] & 0xFF));
}

View File

@@ -5,6 +5,7 @@
#include <stdint.h>
#include <stdbool.h>
#include <ion/screen.h>
#include <kandinsky.h>
#define ST7789_USE_9BIT_SPI 1
@@ -22,10 +23,8 @@ typedef struct {
void st7789_initialize(st7789_t * controller);
void st7789_set_drawing_area(st7789_t * controller,
int16_t x, int16_t y,
int16_t width, int16_t height);
void st7789_set_drawing_area(st7789_t * controller, KDRect area);
void st7789_push_pixels(st7789_t * controller,
ion_color_t * pixels, int32_t numberOfPixels);
const KDColor * pixels, size_t numberOfPixels);
#endif

View File

@@ -2,14 +2,32 @@
#include <stdlib.h>
#include <FL/fl_draw.H>
FltkLCD::FltkLCD(int x, int y, int w, int h, const char * label) :
Fl_Widget(x, y, w, h, label) {
m_framebuffer = malloc(w*h*3);
// FIXME: Delete the framebuffer!
FltkLCD::FltkLCD(int x, int y, int w, int h, uint16_t * rgb565FrameBuffer) :
Fl_Widget(x, y, w, h, nullptr),
m_rgb565frameBufferStart(rgb565FrameBuffer),
m_rgb565frameBufferEnd(rgb565FrameBuffer+w*h)
{
m_rgb888frameBufferStart = malloc(w*h*3);
}
FltkLCD::~FltkLCD() {
free(m_rgb888frameBufferStart);
}
void FltkLCD::draw() {
fl_draw_image((const uchar *)m_framebuffer,
// 1/ Convert the framebuffer from 565 to 888
KDColor * rgb565Pixel = m_rgb565frameBufferStart;
uint8_t * rgb888Component = (uint8_t *)m_rgb888frameBufferStart;
while(rgb565Pixel < m_rgb565frameBufferEnd) {
KDColor color = *rgb565Pixel++;
*rgb888Component++ = KDColorRedComponent(color);
*rgb888Component++ = KDColorGreenComponent(color);
*rgb888Component++ = KDColorBlueComponent(color);
}
// 2/ Draw the 888 framebuffer
fl_draw_image((const uchar *)m_rgb888frameBufferStart,
x(), // x
y(), // y
w(), // width

View File

@@ -2,13 +2,18 @@
#define ION_FLTK_LCD
#include <FL/Fl_Widget.H>
#include <kandinsky.h>
class FltkLCD : public Fl_Widget {
public:
FltkLCD(int x, int y, int w, int h, const char * label = 0);
void * m_framebuffer;
FltkLCD(int x, int y, int w, int h, KDColor * rgb565FrameBuffer);
~FltkLCD();
protected:
void draw();
private:
KDColor * m_rgb565frameBufferStart;
KDColor * m_rgb565frameBufferEnd;
void * m_rgb888frameBufferStart;
};
#endif

View File

@@ -15,6 +15,7 @@ extern "C" {
static FltkLCD * sDisplay;
static FltkKbd * sKeyboard;
static KDFrameBuffer sFrameBuffer;
#define FRAMEBUFFER_ADDRESS (sDisplay->m_framebuffer)
@@ -28,34 +29,34 @@ void init_platform() {
Fl_Window * window = new Fl_Window(screen_width+2*margin, margin+screen_height+margin+keyboard_height+margin);
sDisplay = new FltkLCD(margin, margin, screen_width, screen_height);
sFrameBuffer.pixels = (KDColor *)malloc(ION_SCREEN_WIDTH*ION_SCREEN_HEIGHT*2);
sFrameBuffer.size.width = ION_SCREEN_WIDTH;
sFrameBuffer.size.height = ION_SCREEN_HEIGHT;
/*
sFrameBuffer.drawingArea.origin = KDPointZero;
sFrameBuffer.drawingArea.size = sFrameBuffer.size;
sFrameBuffer.drawingCursor = KDPointZero;
*/
sDisplay = new FltkLCD(margin, margin, screen_width, screen_height, sFrameBuffer.pixels);
sKeyboard = new FltkKbd(margin, margin+screen_height+margin, screen_width, keyboard_height);
window->end();
window->show();
KDCurrentContext->fillRect = NULL;
//KDCurrentContext->fillRect = NULL;
}
void ion_set_pixel(uint16_t x, uint16_t y, ion_color_t color) {
assert(x < ION_SCREEN_WIDTH);
assert(y < ION_SCREEN_HEIGHT);
uint8_t * pixel = (uint8_t *)(FRAMEBUFFER_ADDRESS) + 3*((y*ION_SCREEN_WIDTH)+x);
uint8_t red = (color >> 11) << 3;
uint8_t green = ((color >> 5) & 0x3F) << 2;
uint8_t blue = (color & 0x1F) << 3;
*pixel++ = red;
*pixel++ = green;
*pixel++ = blue;
void ion_screen_push_rect(KDRect rect, const KDColor * pixels) {
KDFramePushRect(&sFrameBuffer, rect, pixels);
}
void ion_fill_rect(
uint16_t x, uint16_t y,
uint16_t width, uint16_t height,
ion_color_t * pattern, size_t patternSize)
{
assert(false);
void ion_screen_push_rect_uniform(KDRect rect, KDColor color) {
KDFramePushRectUniform(&sFrameBuffer, rect, color);
}
void ion_screen_pull_rect(KDRect rect, KDColor * pixels) {
KDFramePullRect(&sFrameBuffer, rect, pixels);
}
bool ion_key_down(ion_key_t key) {

View File

@@ -1,7 +1,9 @@
SFLAGS += -Ikandinsky/include
objs += $(addprefix kandinsky/src/,\
color.o\
context.o\
font.o\
framebuffer.o\
line.o\
pixel.o\
rect.o\

View File

@@ -3,6 +3,7 @@
#include <kandinsky/color.h>
#include <kandinsky/context.h>
#include <kandinsky/framebuffer.h>
#include <kandinsky/line.h>
#include <kandinsky/pixel.h>
#include <kandinsky/rect.h>

View File

@@ -11,8 +11,13 @@ typedef uint16_t KDColor;
* Given KDColor is RGB565 and that we take 8-bit values for R, G, and B,
* some shifting has to happen. */
// FIXME: KDColorRGB(rgb), as a single 32-bits value
#define KDColorRGB(r,g,b) ((KDColor)((((((uint16_t)(r))>>3)&0x1F)<<11)|(((((uint16_t)(g))>>2)&0x3F)<<5)|((((uint16_t)(b))>>3)&0x1F)))
#define KDColorRedComponent(color) ((uint8_t)(((color)>>11)<<3))
#define KDColorGreenComponent(color) ((uint8_t)((((color)>>5)&0x3F)<<2))
#define KDColorBlueComponent(color) ((uint8_t)(((color)&0x1F)<<3))
#define KDColorGray(i) KDColorRGB(i,i,i)
#define KDColorBlack KDColorRGB(0x00, 0x00, 0x00)
@@ -22,4 +27,8 @@ typedef uint16_t KDColor;
#define KDColorGreen KDColorRGB(0x00, 0xFF, 0x00)
#define KDColorBlue KDColorRGB(0x00, 0x00, 0xFF)
// alpha = 0 -> only a
// alpha = 0xFF -> only b
KDColor KDColorBlend(KDColor background, KDColor foreground, uint8_t alpha);
#endif

View File

@@ -5,12 +5,11 @@
#include "rect.h"
typedef struct {
void (*setPixel)(KDCoordinate x, KDCoordinate y, KDColor color);
// fillRect can be left NULL.
// In that case, Kandinsky will fall back to using setPixel only
void (*fillRect)(KDCoordinate x, KDCoordinate y,
KDCoordinate width, KDCoordinate height,
KDColor * pattern, size_t patternSize);
struct {
void (*pushRect)(KDRect rect, const KDColor * pixels);
void (*pushRectUniform)(KDRect rect, KDColor color);
void (*pullRect)(KDRect rect, KDColor * pixels);
} io;
KDPoint origin;
KDRect clippingRect;
} KDContext;

View File

@@ -0,0 +1,17 @@
#ifndef KANDINSKY_FRAMEBUFFER_H
#define KANDINSKY_FRAMEBUFFER_H
#include <kandinsky/color.h>
#include <kandinsky/types.h>
#include <kandinsky/rect.h>
typedef struct {
KDColor * pixels;
KDSize size;
} KDFrameBuffer;
void KDFramePushRect(KDFrameBuffer * frameBuffer, KDRect rect, const KDColor * pixels);
void KDFramePushRectUniform(KDFrameBuffer * frameBuffer, KDRect rect, KDColor color);
void KDFramePullRect(KDFrameBuffer * frameBuffer, KDRect rect, KDColor * pixels);
#endif

View File

@@ -6,4 +6,6 @@
void KDDrawLine(KDPoint p1, KDPoint p2, KDColor c);
void KDDrawAntiAliasedLine(KDPoint p1, KDPoint p2, KDCoordinate width, KDColor frontColor, KDColor backColor);
#endif

View File

@@ -5,5 +5,6 @@
#include <kandinsky/color.h>
void KDSetPixel(KDPoint p, KDColor c);
KDColor KDGetPixel(KDPoint p);
#endif

View File

@@ -34,7 +34,9 @@ KDRect KDRectUnion(KDRect r1, KDRect r2);
bool KDRectContains(KDRect r, KDPoint p);
KDRect KDRectTranslate(KDRect r, KDPoint p);
void KDFillRect(KDRect rect, const KDColor * pattern, size_t patternSize);
void KDFillRect(KDRect rect, KDColor color);
void KDFillRectWithPixels(KDRect rect, const KDColor * pixels, KDColor * workingBuffer);
void KDFillRectWithMask(KDRect rect, KDColor color, const uint8_t * mask, KDColor * workingBuffer);
void KDDrawRect(KDRect rect, KDColor color);
#endif

11
kandinsky/src/color.c Normal file
View File

@@ -0,0 +1,11 @@
#include <kandinsky/color.h>
#define KDAlphaBlend(c1,c2,a) ((uint8_t)((((uint16_t)c1*(0xFF-a))+((uint16_t)c2*a))/0xFF))
KDColor KDColorBlend(KDColor a, KDColor b, uint8_t alpha) {
return KDColorRGB(
KDAlphaBlend(KDColorRedComponent(a), KDColorRedComponent(b), alpha),
KDAlphaBlend(KDColorGreenComponent(a), KDColorGreenComponent(b), alpha),
KDAlphaBlend(KDColorBlueComponent(a), KDColorBlueComponent(b), alpha)
);
}

View File

@@ -2,8 +2,11 @@
#include <ion.h>
KDContext KDIonContext = {
.setPixel = &ion_set_pixel,
.fillRect = &ion_fill_rect,
.io = {
.pushRect = &ion_screen_push_rect,
.pushRectUniform = &ion_screen_push_rect_uniform,
.pullRect = &ion_screen_pull_rect
},
.origin = {
.x = 0,
.y = 0

View File

@@ -0,0 +1,34 @@
#include <kandinsky/framebuffer.h>
#include <string.h>
static inline KDColor * frameBufferPixelAddress(KDFrameBuffer * frameBuffer, KDCoordinate x, KDCoordinate y) {
return frameBuffer->pixels + x + y*frameBuffer->size.width;
}
void KDFramePushRect(KDFrameBuffer * frameBuffer, KDRect rect, const KDColor * pixels) {
const KDColor * line = pixels;
for (int j=0; j<rect.height; j++) {
memcpy(frameBufferPixelAddress(frameBuffer, rect.x, rect.y+j),
line,
rect.width*sizeof(KDColor));
line += rect.width;
}
}
void KDFramePushRectUniform(KDFrameBuffer * frameBuffer, KDRect rect, KDColor color) {
for (int j=0; j<rect.height; j++) {
for (int i=0; i<rect.width; i++) {
*frameBufferPixelAddress(frameBuffer, rect.x+i, rect.y+j) = color;
}
}
}
void KDFramePullRect(KDFrameBuffer * frameBuffer, KDRect rect, KDColor * pixels) {
KDColor * line = pixels;
for (int j=0; j<rect.height; j++) {
memcpy(line,
frameBufferPixelAddress(frameBuffer, rect.x, rect.y+j),
rect.width*sizeof(KDColor));
line += rect.width;
}
}

View File

@@ -58,3 +58,43 @@ void KDDrawLine(KDPoint p1, KDPoint p2, KDColor c) {
}
}
}
/*
#include <math.h>
#include <stdlib.h>
void KDDrawAntiAliasedLine(KDPoint p1, KDPoint p2, KDCoordinate width, KDColor frontColor, KDColor backColor) {
int x0 = p1.x;
int y0 = p1.y;
int x1 = p2.x;
int y1 = p2.y;
float wd = width;
int dx = abs(x1-x0), sx = x0 < x1 ? 1 : -1;
int dy = abs(y1-y0), sy = y0 < y1 ? 1 : -1;
int err = dx-dy, e2, x2, y2; // error value e_xy
float ed = dx+dy == 0 ? 1 : sqrt((float)dx*dx+(float)dy*dy);
for (wd = (wd+1)/2; ; ) { // pixel loop
KDPoint p = {.x = x0, .y = y0};
KDColor color = KDColorMix(KDColorRed, KDColorWhite, (abs(err-dx+dy)/ed-wd+1));
KDSetPixel(p, color);
e2 = err; x2 = x0;
if (2*e2 >= -dx) { // x step
for (e2 += dy, y2 = y0; e2 < ed*wd && (y1 != y2 || dx > dy); e2 += dx) {
y2 += sy;
p = {.x = x0, .y = y2};
color = KDColorMix(KDColorRed, KDColorWhite, (abs(err-dx+dy)/ed-wd+1));
setPixelColor(x0, y2 += sy, max(0,255*(abs(e2)/ed-wd+1)));
}
if (x0 == x1) break;
e2 = err; err -= dy; x0 += sx;
}
if (2*e2 <= dy) { // y step
for (e2 = dx-e2; e2 < ed*wd && (x1 != x2 || dx < dy); e2 += dy)
setPixelColor(x2 += sx, y0, max(0,255*(abs(e2)/ed-wd+1)));
if (y0 == y1) break;
err += dx; y0 += sy;
}
}
}
*/

View File

@@ -7,6 +7,29 @@
void KDSetPixel(KDPoint p, KDColor c) {
KDPoint absolutePoint = KDPointTranslate(p, KDCurrentContext->origin);
if (KDRectContains(KDCurrentContext->clippingRect, absolutePoint)) {
KDCurrentContext->setPixel(absolutePoint.x, absolutePoint.y, c);
KDCurrentContext->io.pushRect((KDRect){.origin = absolutePoint, .width = 1, .height = 1}, &c);
}
}
KDColor KDGetPixel(KDPoint p) {
KDPoint absolutePoint = KDPointTranslate(p, KDCurrentContext->origin);
if (KDRectContains(KDCurrentContext->clippingRect, absolutePoint)) {
KDColor result;
KDCurrentContext->io.pullRect((KDRect){.origin = absolutePoint, .width = 1, .height = 1}, &result);
return result;
}
return KDColorBlack;
}
/*
void KDSetAbsolutePixelDirect(KDPoint p, KDColor c) {
KDCurrentContext->io.setWorkingArea((KDRect){.origin = p, .width = 1, .height = 1});
KDCurrentContext->io.pushPixels(&c, 1);
}
KDColor KDGetAbsolutePixelDirect(KDPoint p) {
KDColor result;
KDCurrentContext->io.setWorkingArea((KDRect){.origin = p, .width = 1, .height = 1});
KDCurrentContext->io.pullPixels(&result, 1);
return result;
}
*/

View File

@@ -101,38 +101,150 @@ KDRect KDRectTranslate(KDRect r, KDPoint p) {
};
}
void KDPerPixelFillRect(KDCoordinate x, KDCoordinate y,
KDCoordinate width, KDCoordinate height,
KDColor * pattern, size_t patternSize) {
size_t offset = 0;
for (KDCoordinate j=0; j<height; j++) {
for (KDCoordinate i=0; i<width; i++) {
KDCurrentContext->setPixel(x+i, y+j, pattern[offset++]);
if (offset >= patternSize) {
offset = 0;
}
KDRect absoluteFillRect(KDRect rect) {
KDRect absolutRect = rect;
absolutRect.origin = KDPointTranslate(absolutRect.origin, KDCurrentContext->origin);
KDRect rectToBeFilled = KDRectIntersection(absolutRect, KDCurrentContext->clippingRect);
return rectToBeFilled;
}
void KDFillRect(KDRect rect, KDColor color) {
KDRect absoluteRect = absoluteFillRect(rect);
if (absoluteRect.width > 0 && absoluteRect.height > 0) {
KDCurrentContext->io.pushRectUniform(absoluteRect, color);
}
}
/* Note: we support the case where workingBuffer IS equal to pixels */
void KDFillRectWithPixels(KDRect rect, const KDColor * pixels, KDColor * workingBuffer) {
KDRect absoluteRect = absoluteFillRect(rect);
if (absoluteRect.width == 0 || absoluteRect.height == 0) {
return;
}
/* Caution:
* The absoluteRect may have a SMALLER size than the original rect because it
* has been clipped. Therefore we cannot assume that the mask can be read as a
* continuous area. */
if (absoluteRect.width == rect.width && absoluteRect.height == rect.height) {
KDCurrentContext->io.pushRect(absoluteRect, pixels);
return;
}
/* At this point we need the working buffer */
assert(workingBuffer != NULL);
for (KDCoordinate j=0; j<absoluteRect.height; j++) {
for (KDCoordinate i=0; i<absoluteRect.width; i++) {
workingBuffer[i+absoluteRect.width*j] = pixels[i+rect.width*j];
}
}
KDCurrentContext->io.pushRect(absoluteRect, workingBuffer);
}
// Mask's size must be rect.size
// WorkingBuffer, same deal
void KDFillRectWithMask(KDRect rect, KDColor color, const uint8_t * mask, KDColor * workingBuffer) {
KDRect absoluteRect = absoluteFillRect(rect);
/* Caution:
* The absoluteRect may have a SMALLER size than the original rect because it
* has been clipped. Therefore we cannot assume that the mask can be read as a
* continuous area. */
KDCurrentContext->io.pullRect(absoluteRect, workingBuffer);
for (KDCoordinate j=0; j<absoluteRect.height; j++) {
for (KDCoordinate i=0; i<absoluteRect.width; i++) {
KDColor * currentPixelAdress = workingBuffer + i + absoluteRect.width*j;
const uint8_t * currentMaskAddress = mask + i + rect.width*j;
*currentPixelAdress = KDColorBlend(*currentPixelAdress, color, *currentMaskAddress);
}
}
KDCurrentContext->io.pushRect(absoluteRect, workingBuffer);
}
#if 0
/* Takes an absolute rect and pushes the pixels */
/* Does the pattern-to-continuous-memory conversion */
void pushRect(KDRect rect, const KDColor * pattern, KDSize patternSize) {
KDCurrentContext->io.setWorkingArea(rect);
//#define ION_DEVICE_FILL_RECT_FAST_PATH 1
#if ION_DEVICE_FILL_RECT_FAST_PATH
/* If the pattern width matches the target rect width, we can easily push
* mutliple lines at once since those will be contiguous in memory. */
if (patternSize.width == rect.width) {
size_t remainingPixelCount = rect.width*rect.height;
size_t patternPixelCount = patternSize.width*patternSize.height;
while (remainingPixelCount > 0) {
int32_t blockSize = remainingPixelCount < patternPixelCount ? remainingPixelCount : patternPixelCount;
KDCurrentContext->io.pushPixels(pattern, blockSize);
remainingPixelCount -= blockSize;
}
return;
}
#endif
uint16_t remainingHeight = rect.height;
uint16_t patternLine = 0;
while (remainingHeight-- > 0) {
uint16_t remainingWidth = rect.width;
while (remainingWidth > 0) {
int32_t blockSize = remainingWidth < patternSize.width ? remainingWidth : patternSize.width;
KDCurrentContext->io.pushPixels(pattern + patternLine*patternSize.width, blockSize);
remainingWidth -= blockSize;
}
if (++patternLine >= patternSize.height) {
patternLine = 0;
}
}
}
void KDFillRect(KDRect rect, const KDColor * pattern, size_t patternSize) {
assert(patternSize >= 1);
void KDBlitRect(KDRect rect, const KDColor * pattern, KDSize patternSize, const uint8_t * mask, KDSize maskSize) {
assert(pattern != NULL && patternSize.width > 0 && patternSize.height > 0);
KDRect absolutRect = rect;
absolutRect.origin = KDPointTranslate(absolutRect.origin, KDCurrentContext->origin);
KDRect rectToBeFilled = KDRectIntersection(absolutRect, KDCurrentContext->clippingRect);
void (*fillRectFunction)(KDCoordinate x, KDCoordinate y,
KDCoordinate width, KDCoordinate height,
KDColor * pattern, size_t patternSize) = KDCurrentContext->fillRect;
if (fillRectFunction == NULL) {
fillRectFunction = KDPerPixelFillRect;
bool useBlending = (mask != NULL && maskSize.width > 0 && maskSize.height > 0);
if (!useBlending) {
pushRect(rectToBeFilled, pattern, patternSize);
return;
}
fillRectFunction(rectToBeFilled.x, rectToBeFilled.y,
rectToBeFilled.width, rectToBeFilled.height,
pattern, patternSize);
assert(KDCurrentContext->io.pullPixels != NULL);
KDCoordinate patternX = 0, patternY = 0, maskX = 0, maskY = 0;
for (KDCoordinate j=0; j<rectToBeFilled.height; j++) {
for (KDCoordinate i=0; i<rectToBeFilled.width; i++) {
KDColor foregroundColor = pattern[patternX+patternSize.width*patternY];
KDPoint p = {
.x = rectToBeFilled.x + i,
.y = rectToBeFilled.y + j
};
KDColor backgroundColor = KDGetAbsolutePixelDirect(p);
uint8_t alpha = mask[maskX+maskSize.width*maskY];
KDColor newColor = KDColorBlend(backgroundColor, foregroundColor, alpha);
KDSetAbsolutePixelDirect(p, newColor);
if (++patternX >= patternSize.width) {
patternX = 0;
}
if (++maskX >= maskSize.width) {
maskX = 0;
}
}
if (++patternY >= patternSize.height) {
patternY = 0;
}
if (++maskY >= maskSize.height) {
maskY = 0;
}
}
}
#endif
void KDDrawRect(KDRect rect, KDColor color) {
KDPoint p1, p2;

View File

@@ -4,3 +4,7 @@ liba/src/external/sqlite/mem5.o: CFLAGS += -w
objs += $(addprefix liba/src/, assert.o errno.o malloc.o memcpy.o memset.o powf.o strcmp.o strlen.o external/sqlite/mem5.o)
tests += $(addprefix liba/test/, stdint.c)
# The use of aeabi-rt could be made conditional to an AEABI target.
# In practice we're always using liba on such a target.
objs += $(addprefix liba/src/aeabi-rt/, memcpy.o)

View File

@@ -0,0 +1,16 @@
We're using the ARM Embedded ABI (AEABI) to build our code for the Cortex-M.
That ABI specifies a lot of things: how procedures are called, how ELF files
should be structured, and last but not least, a run-time.
That run-time consists in a list of helper functions that the compiler is free
to call anywhere : unaligned memory accesses, integer division functions,
memory copying/clearing/setting, etc...
Since we're telling our compiler to build an AEABI binary, it may decide to use
those symbols, and so we have to provide an implementation for them. Refer to
the "Run-time ABI for the ARM Architecture" for a full list of functions.
Note that this is not formally the job of a libc implementation. Similar code is
often shipped alongside a compiler (LLVM calls it compiler-rt, GCC libgcc). But
since we're using so few symbols in practice, it makes sense to glue it in. Also
some libc do also implement this (newlib, for example).

View File

@@ -0,0 +1,8 @@
#include <stddef.h>
#include <string.h>
/* See the "Run-time ABI for the ARM Architecture", Section 4.3.3 */
void __aeabi_memcpy(void * dest, const void * src, size_t n) {
memcpy(dest, src, n);
}