blob: 7745071225c0c59b24ab78901759bab29f414458 [file] [log] [blame]
// Copyright 2016 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* @defgroup graphics Graphics
*
* @{
*/
/**
* @file
* @brief Graphics drawing library
*/
#include <assert.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <zircon/compiler.h>
#include <zircon/syscalls.h>
#include <gfx-common/gfx-common.h>
#define GFX_LOG(ctx, fmt, ...) \
do { \
(ctx)->log("[%s:%d] " fmt, __FILE__, __LINE__, ##__VA_ARGS__); \
} while (0)
#define GFX_ASSERT_MSG(ctx, cond, fmt, ...) \
do { \
if (!(cond)) { \
(ctx)->panic("[%s:%d]" fmt, __FILE__, __LINE__, ##__VA_ARGS__); \
} \
} while (0)
#define GFX_ASSERT(ctx, cond) \
do { \
if (!(cond)) { \
(ctx)->panic("[%s:%d] failed assertion: " #cond "\n", __FILE__, __LINE__); \
} \
} while (0)
namespace {
struct Rgb888 {
Rgb888() = default;
explicit Rgb888(uint32_t rgba) {
b = rgba & 0xff;
g = (rgba & 0xff00) >> 8;
r = (rgba & 0xff0000) >> 16;
}
uint32_t ToRgba32() const { return b | (g << 8) | (r << 16) | (0xff << 24); }
uint8_t b = 0;
uint8_t g = 0;
uint8_t r = 0;
} __attribute__((packed));
} // namespace
// Convert a 32bit ARGB image to its respective gamma corrected grayscale value.
static uint32_t ARGB8888_to_Luma(uint32_t in) {
uint8_t out;
uint32_t blue = (in & 0xFF) * 74;
uint32_t green = ((in >> 8) & 0xFF) * 732;
uint32_t red = ((in >> 16) & 0xFF) * 218;
uint32_t intensity = red + blue + green;
out = (intensity >> 10) & 0xFF;
return out;
}
static uint32_t ARGB8888_to_RGB888(uint32_t in) { return in & 0xFFFFFF; }
static uint32_t ARGB8888_to_RGB565(uint32_t in) {
uint32_t out;
out = (in >> 3) & 0x1f; // b
out |= ((in >> 10) & 0x3f) << 5; // g
out |= ((in >> 19) & 0x1f) << 11; // r
return out;
}
static uint32_t ARGB8888_to_RGB332(uint32_t in) {
uint32_t out = 0;
out = (in >> 6) & 0x3; // b
out |= ((in >> 13) & 0x7) << 2; // g
out |= ((in >> 21) & 0x7) << 5; // r
return out;
}
static uint32_t ARGB8888_to_RGB2220(uint32_t in) {
uint32_t out = 0;
out = ((in >> 6) & 0x3) << 2;
out |= ((in >> 14) & 0x3) << 4;
out |= ((in >> 22) & 0x3) << 6;
return out;
}
/**
* @brief Copy a rectangle of pixels from one part of the display to another.
*/
void gfx_copyrect(gfx_surface* surface, uint32_t x, uint32_t y, uint32_t width, uint32_t height,
uint32_t x2, uint32_t y2) {
// trim
if (x >= surface->width)
return;
if (x2 >= surface->width)
return;
if (y >= surface->height)
return;
if (y2 >= surface->height)
return;
if (width == 0 || height == 0)
return;
// clip the width to src or dest
if (x + width > surface->width)
width = surface->width - x;
if (x2 + width > surface->width)
width = surface->width - x2;
// clip the height to src or dest
if (y + height > surface->height)
height = surface->height - y;
if (y2 + height > surface->height)
height = surface->height - y2;
surface->copyrect(surface, x, y, width, height, x2, y2);
}
/**
* @brief Fill a rectangle on the screen with a constant color.
*/
void gfx_fillrect(gfx_surface* surface, uint32_t x, uint32_t y, uint32_t width, uint32_t height,
uint32_t color) {
GFX_LOG(surface->ctx, "surface %p, x %u y %u w %u h %u c %u\n", surface, x, y, width, height,
color);
// trim
if (unlikely(x >= surface->width))
return;
if (y >= surface->height)
return;
if (width == 0 || height == 0)
return;
// clip the width
if (x + width > surface->width)
width = surface->width - x;
// clip the height
if (y + height > surface->height)
height = surface->height - y;
surface->fillrect(surface, x, y, width, height, color);
}
/**
* @brief Write a single pixel to the screen.
*/
void gfx_putpixel(gfx_surface* surface, uint32_t x, uint32_t y, uint32_t color) {
if (unlikely(x >= surface->width))
return;
if (y >= surface->height)
return;
surface->putpixel(surface, x, y, color);
}
template <typename T>
static void putpixel(gfx_surface* surface, uint32_t x, uint32_t y, uint32_t color) {
T* dest = static_cast<T*>(surface->ptr) + (x + y * surface->stride);
if (sizeof(T) == sizeof(uint32_t)) {
*dest = static_cast<T>(color);
} else {
// colors come in in ARGB 8888 form, flatten them
*dest = static_cast<T>(surface->translate_color(color));
}
}
template <typename T>
static void copyrect(gfx_surface* surface, uint32_t x, uint32_t y, uint32_t width, uint32_t height,
uint32_t x2, uint32_t y2) {
// copy
const T* src = static_cast<const T*>(surface->ptr) + (x + y * surface->stride);
T* dest = static_cast<T*>(surface->ptr) + (x2 + y2 * surface->stride);
uint32_t stride_diff = surface->stride - width;
if (dest < src) {
uint32_t i, j;
for (i = 0; i < height; i++) {
for (j = 0; j < width; j++) {
*dest = *src;
dest++;
src++;
}
dest += stride_diff;
src += stride_diff;
}
} else {
// copy backwards
src += (height - 1) * surface->stride + (width - 1);
dest += (height - 1) * surface->stride + (width - 1);
uint32_t i, j;
for (i = 0; i < height; i++) {
for (j = 0; j < width; j++) {
*dest = *src;
dest--;
src--;
}
dest -= stride_diff;
src -= stride_diff;
}
}
}
template <typename T>
static void fillrect(gfx_surface* surface, uint32_t x, uint32_t y, uint32_t width, uint32_t height,
uint32_t _color) {
T* dest = static_cast<T*>(surface->ptr) + (x + y * surface->stride);
uint32_t stride_diff = surface->stride - width;
T color;
if (sizeof(_color) == sizeof(color)) {
color = static_cast<T>(_color);
} else {
color = static_cast<T>(surface->translate_color(_color));
}
uint32_t i, j;
for (i = 0; i < height; i++) {
for (j = 0; j < width; j++) {
*dest = color;
dest++;
}
dest += stride_diff;
}
}
void gfx_line(gfx_surface* surface, uint32_t x1, uint32_t y1, uint32_t x2, uint32_t y2,
uint32_t color) {
if (unlikely(x1 >= surface->width))
return;
if (unlikely(x2 >= surface->width))
return;
if (y1 >= surface->height)
return;
if (y2 >= surface->height)
return;
int dx = x2 - x1;
int dy = y2 - y1;
int sdx = (0 < dx) - (dx < 0);
int sdy = (0 < dy) - (dy < 0);
uint32_t dxabs = (dx > 0) ? dx : -dx;
uint32_t dyabs = (dy > 0) ? dy : -dy;
uint32_t x = dyabs >> 1;
uint32_t y = dxabs >> 1;
uint32_t px = x1;
uint32_t py = y1;
if (dxabs >= dyabs) {
// mostly horizontal line.
for (uint32_t i = 0; i < dxabs; i++) {
y += dyabs;
if (y >= dxabs) {
y -= dxabs;
py += sdy;
}
px += sdx;
surface->putpixel(surface, px, py, color);
}
} else {
// mostly vertical line.
for (uint32_t i = 0; i < dyabs; i++) {
x += dxabs;
if (x >= dyabs) {
x -= dyabs;
px += sdx;
}
py += sdy;
surface->putpixel(surface, px, py, color);
}
}
}
static uint32_t alpha32_add_ignore_destalpha(uint32_t dest, uint32_t src) {
uint32_t cdest[3];
uint32_t csrc[3];
uint32_t srca;
uint32_t srcainv;
srca = (src >> 24) & 0xff;
if (srca == 0) {
return dest;
} else if (srca == 255) {
return src;
}
srca++;
srcainv = (255 - srca);
cdest[0] = (dest >> 16) & 0xff;
cdest[1] = (dest >> 8) & 0xff;
cdest[2] = (dest >> 0) & 0xff;
csrc[0] = (src >> 16) & 0xff;
csrc[1] = (src >> 8) & 0xff;
csrc[2] = (src >> 0) & 0xff;
// if (srca > 0)
// printf("s %d %d %d d %d %d %d a %d ai %d\n", csrc[0], csrc[1], csrc[2], cdest[0],
// cdest[1], cdest[2], srca, srcainv);
uint32_t cres[3];
cres[0] = ((csrc[0] * srca) / 256) + ((cdest[0] * srcainv) / 256);
cres[1] = ((csrc[1] * srca) / 256) + ((cdest[1] * srcainv) / 256);
cres[2] = ((csrc[2] * srca) / 256) + ((cdest[2] * srcainv) / 256);
return (srca << 24) | (cres[0] << 16) | (cres[1] << 8) | (cres[2]);
}
/**
* @brief Copy pixels from source to dest.
*
* Currently does not support alpha channel.
*/
void gfx_surface_blend(struct gfx_surface* target, struct gfx_surface* source, uint32_t destx,
uint32_t desty) {
gfx_blend(target, source, 0, 0, source->width, source->height, destx, desty);
}
void gfx_blend(struct gfx_surface* target, struct gfx_surface* source, uint32_t srcx, uint32_t srcy,
uint32_t width, uint32_t height, uint32_t destx, uint32_t desty) {
const gfx_context* ctx = source->ctx;
GFX_ASSERT(ctx, target->format == source->format);
GFX_LOG(ctx, "target %p, source %p, destx %u, desty %u\n", target, source, destx, desty);
if (destx >= target->width)
return;
if (desty >= target->height)
return;
if (srcx >= source->width)
return;
if (srcy >= source->height)
return;
if (destx + width > target->width)
width = target->width - destx;
if (desty + height > target->height)
height = target->height - desty;
if (srcx + width > source->width)
width = source->width - srcx;
if (srcy + height > source->height)
height = source->height - srcy;
// XXX total hack to deal with various blends
if (source->format == ZX_PIXEL_FORMAT_RGB_565 && target->format == ZX_PIXEL_FORMAT_RGB_565) {
// 16 bit to 16 bit
const uint16_t* src = static_cast<uint16_t*>(source->ptr) + (srcx + srcy * source->stride);
uint16_t* dest = static_cast<uint16_t*>(target->ptr) + (destx + desty * target->stride);
uint32_t dest_stride_diff = target->stride - width;
uint32_t source_stride_diff = source->stride - width;
GFX_LOG(ctx, "w %u h %u dstride %u sstride %u\n", width, height, dest_stride_diff,
source_stride_diff);
uint32_t i, j;
for (i = 0; i < height; i++) {
for (j = 0; j < width; j++) {
*dest = *src;
dest++;
src++;
}
dest += dest_stride_diff;
src += source_stride_diff;
}
} else if (source->format == ZX_PIXEL_FORMAT_ARGB_8888 &&
target->format == ZX_PIXEL_FORMAT_ARGB_8888) {
// both are 32 bit modes, both alpha
const uint32_t* src = static_cast<uint32_t*>(source->ptr) + (srcx + srcy * source->stride);
uint32_t* dest = static_cast<uint32_t*>(target->ptr) + (destx + desty * target->stride);
uint32_t dest_stride_diff = target->stride - width;
uint32_t source_stride_diff = source->stride - width;
GFX_LOG(ctx, "w %u h %u dstride %u sstride %u\n", width, height, dest_stride_diff,
source_stride_diff);
uint32_t i, j;
for (i = 0; i < height; i++) {
for (j = 0; j < width; j++) {
// TODO(fxbug.dev/84457): Currently it ignores destination alpha.
// We should implement alpha blending correctly.
*dest = alpha32_add_ignore_destalpha(*dest, *src);
dest++;
src++;
}
dest += dest_stride_diff;
src += source_stride_diff;
}
} else if (source->format == ZX_PIXEL_FORMAT_RGB_x888 &&
target->format == ZX_PIXEL_FORMAT_RGB_x888) {
// both are 32 bit modes, no alpha
const uint32_t* src = static_cast<uint32_t*>(source->ptr) + (srcx + srcy * source->stride);
uint32_t* dest = static_cast<uint32_t*>(target->ptr) + (destx + desty * target->stride);
uint32_t dest_stride_diff = target->stride - width;
uint32_t source_stride_diff = source->stride - width;
GFX_LOG(ctx, "w %u h %u dstride %u sstride %u\n", width, height, dest_stride_diff,
source_stride_diff);
uint32_t i, j;
for (i = 0; i < height; i++) {
for (j = 0; j < width; j++) {
*dest = *src;
dest++;
src++;
}
dest += dest_stride_diff;
src += source_stride_diff;
}
} else if (source->format == ZX_PIXEL_FORMAT_ARGB_8888 &&
target->format == ZX_PIXEL_FORMAT_RGB_888) {
// 32 bit to 24 bit modes, alpha to no-alpha
const uint32_t* src = static_cast<uint32_t*>(source->ptr) + (srcx + srcy * source->stride);
Rgb888* dest = static_cast<Rgb888*>(target->ptr) + (destx + desty * target->stride);
uint32_t dest_stride_diff = target->stride - width;
uint32_t source_stride_diff = source->stride - width;
GFX_LOG(ctx, "w %u h %u dstride %u sstride %u\n", width, height, dest_stride_diff,
source_stride_diff);
uint32_t i, j;
for (i = 0; i < height; i++) {
for (j = 0; j < width; j++) {
// TODO(fxbug.dev/84457): Currently it ignores destination alpha.
// We should implement alpha blending correctly.
*dest = Rgb888(alpha32_add_ignore_destalpha(dest->ToRgba32(), *src));
dest++;
src++;
}
dest += dest_stride_diff;
src += source_stride_diff;
}
} else if (source->format == ZX_PIXEL_FORMAT_RGB_x888 &&
target->format == ZX_PIXEL_FORMAT_RGB_888) {
// 32 bit to 24 bit modes, no alpha
const uint32_t* src = static_cast<uint32_t*>(source->ptr) + (srcx + srcy * source->stride);
Rgb888* dest = static_cast<Rgb888*>(target->ptr) + (destx + desty * target->stride);
uint32_t dest_stride_diff = target->stride - width;
uint32_t source_stride_diff = source->stride - width;
GFX_LOG(ctx, "w %u h %u dstride %u sstride %u\n", width, height, dest_stride_diff,
source_stride_diff);
uint32_t i, j;
for (i = 0; i < height; i++) {
for (j = 0; j < width; j++) {
*dest = Rgb888(*src);
dest++;
src++;
}
dest += dest_stride_diff;
src += source_stride_diff;
}
} else if (source->format == ZX_PIXEL_FORMAT_MONO_8 && target->format == ZX_PIXEL_FORMAT_MONO_8) {
// both are 8 bit modes, no alpha
const uint8_t* src = static_cast<uint8_t*>(source->ptr) + (srcx + srcy * source->stride);
uint8_t* dest = static_cast<uint8_t*>(target->ptr) + (destx + desty * target->stride);
uint32_t dest_stride_diff = target->stride - width;
uint32_t source_stride_diff = source->stride - width;
GFX_LOG(ctx, "w %u h %u dstride %u sstride %u\n", width, height, dest_stride_diff,
source_stride_diff);
uint32_t i, j;
for (i = 0; i < height; i++) {
for (j = 0; j < width; j++) {
*dest = *src;
dest++;
src++;
}
dest += dest_stride_diff;
src += source_stride_diff;
}
} else {
GFX_ASSERT_MSG(
ctx, false,
"gfx_surface_blend: unimplemented colorspace combination (source %u target %u)\n",
source->format, target->format);
}
}
template <typename T>
static void putchar(gfx_surface* surface, const gfx_font_t* font, uint32_t ch, uint32_t x,
uint32_t y, uint32_t fg, uint32_t bg) {
T* dest = static_cast<T*>(surface->ptr) + (x + y * surface->stride);
const uint16_t* cdata = font->data + ch * font->height;
uint32_t fw = font->width;
for (uint32_t i = font->height; i > 0; i--) {
uint16_t xdata = *cdata++;
for (uint32_t j = fw; j > 0; j--) {
*dest++ = static_cast<T>((xdata & 1) ? fg : bg);
xdata = static_cast<uint16_t>(xdata >> 1);
}
dest += (surface->stride - fw);
}
}
void gfx_putchar(gfx_surface* surface, const gfx_font_t* font, uint32_t ch, uint32_t x, uint32_t y,
uint32_t fg, uint32_t bg) {
if (unlikely(ch > 127)) {
return;
}
if (unlikely(x > (surface->width - font->width))) {
return;
}
if (unlikely(y > (surface->height - font->height))) {
return;
}
if (surface->translate_color) {
fg = surface->translate_color(fg);
bg = surface->translate_color(bg);
}
surface->putchar(surface, font, ch, x, y, fg, bg);
}
void gfx_copylines(gfx_surface* dst, gfx_surface* src, uint32_t srcy, uint32_t dsty,
uint32_t height) {
if ((dst->stride != src->stride) || (dst->format != src->format)) {
return;
}
if ((srcy >= src->height) || ((src->height - srcy) < height)) {
return;
}
if ((dsty >= dst->height) || (dst->height - dsty) < height) {
return;
}
memmove(reinterpret_cast<uint8_t*>(dst->ptr) + dsty * dst->stride * dst->pixelsize,
reinterpret_cast<uint8_t*>(src->ptr) + srcy * src->stride * src->pixelsize,
height * src->stride * src->pixelsize);
}
/**
* @brief Ensure all graphics rendering is sent to display
*/
void gfx_flush(gfx_surface* surface) {
if (surface->flags & GFX_FLAG_FLUSH_CPU_CACHE)
surface->ctx->flush_cache(surface->ptr, surface->len);
if (surface->flush)
surface->flush(0, surface->height - 1);
}
/**
* @brief Ensure that a sub-region of the display is up to date.
*/
void gfx_flush_rows(struct gfx_surface* surface, uint32_t start, uint32_t end) {
if (start > end) {
uint32_t temp = start;
start = end;
end = temp;
}
if (start >= surface->height)
return;
if (end >= surface->height)
end = surface->height - 1;
if (surface->flags & GFX_FLAG_FLUSH_CPU_CACHE) {
uint32_t runlen = surface->stride * surface->pixelsize;
surface->ctx->flush_cache(reinterpret_cast<uint8_t*>(surface->ptr) + start * runlen,
(end - start + 1) * runlen);
}
if (surface->flush)
surface->flush(start, end);
}
/**
* @brief Create a new graphics surface object
*/
gfx_surface* gfx_create_surface_with_context(void* ptr, const gfx_context* ctx, uint32_t width,
uint32_t height, uint32_t stride, gfx_format format,
uint32_t flags) {
gfx_surface* surface = static_cast<gfx_surface*>(calloc(1, sizeof(*surface)));
if (surface == NULL)
return NULL;
surface->ctx = ctx;
if (gfx_init_surface(surface, ptr, width, height, stride, format, flags)) {
free(surface);
return NULL;
}
return surface;
}
zx_status_t gfx_init_surface(gfx_surface* surface, void* ptr, uint32_t width, uint32_t height,
uint32_t stride, gfx_format format, uint32_t flags) {
if ((width == 0) || (height == 0) || (stride < width)) {
return ZX_ERR_INVALID_ARGS;
}
surface->flags = flags;
surface->format = format;
surface->width = width;
surface->height = height;
surface->stride = stride;
surface->alpha = MAX_ALPHA;
// set up some function pointers
switch (format) {
case ZX_PIXEL_FORMAT_RGB_565:
surface->translate_color = &ARGB8888_to_RGB565;
surface->copyrect = &copyrect<uint16_t>;
surface->fillrect = &fillrect<uint16_t>;
surface->putpixel = &putpixel<uint16_t>;
surface->putchar = &putchar<uint16_t>;
surface->pixelsize = 2;
surface->len = (surface->height * surface->stride * surface->pixelsize);
break;
case ZX_PIXEL_FORMAT_RGB_888:
surface->translate_color = &ARGB8888_to_RGB888;
surface->copyrect = &copyrect<Rgb888>;
surface->fillrect = &fillrect<Rgb888>;
surface->putpixel = &putpixel<Rgb888>;
surface->putchar = &putchar<Rgb888>;
surface->pixelsize = 3;
surface->len = (surface->height * surface->stride * surface->pixelsize);
break;
case ZX_PIXEL_FORMAT_RGB_x888:
case ZX_PIXEL_FORMAT_ARGB_8888:
surface->translate_color = NULL;
surface->copyrect = &copyrect<uint32_t>;
surface->fillrect = &fillrect<uint32_t>;
surface->putpixel = &putpixel<uint32_t>;
surface->putchar = &putchar<uint32_t>;
surface->pixelsize = 4;
surface->len = (surface->height * surface->stride * surface->pixelsize);
break;
case ZX_PIXEL_FORMAT_MONO_8:
surface->translate_color = &ARGB8888_to_Luma;
surface->copyrect = &copyrect<uint8_t>;
surface->fillrect = &fillrect<uint8_t>;
surface->putpixel = &putpixel<uint8_t>;
surface->putchar = &putchar<uint8_t>;
surface->pixelsize = 1;
surface->len = (surface->height * surface->stride * surface->pixelsize);
break;
case ZX_PIXEL_FORMAT_RGB_332:
surface->translate_color = &ARGB8888_to_RGB332;
surface->copyrect = &copyrect<uint8_t>;
surface->fillrect = &fillrect<uint8_t>;
surface->putpixel = &putpixel<uint8_t>;
surface->putchar = &putchar<uint8_t>;
surface->pixelsize = 1;
surface->len = (surface->height * surface->stride * surface->pixelsize);
break;
case ZX_PIXEL_FORMAT_RGB_2220:
surface->translate_color = &ARGB8888_to_RGB2220;
surface->copyrect = &copyrect<uint8_t>;
surface->fillrect = &fillrect<uint8_t>;
surface->putpixel = &putpixel<uint8_t>;
surface->putchar = &putchar<uint8_t>;
surface->pixelsize = 1;
surface->len = (surface->height * surface->stride * surface->pixelsize);
break;
default:
GFX_LOG(surface->ctx, "invalid graphics format\n");
return ZX_ERR_INVALID_ARGS;
}
if (ptr == NULL) {
// allocate a buffer
ptr = malloc(surface->len);
if (ptr == NULL) {
return ZX_ERR_NO_MEMORY;
}
GFX_ASSERT(surface->ctx, ptr);
surface->flags |= GFX_FLAG_FREE_ON_DESTROY;
}
surface->ptr = ptr;
return ZX_OK;
}
/**
* @brief Destroy a graphics surface and free all resources allocated to it.
*
* @param surface Surface to destroy. This pointer is no longer valid after
* this call.
*/
void gfx_surface_destroy(struct gfx_surface* surface) {
if (surface->flags & GFX_FLAG_FREE_ON_DESTROY)
free(surface->ptr);
free(surface);
}