#include <string>
#include <vector>
#include <cstdarg>
#include <cstdio>
#include "Painter.h"
#include "Graphics.h"
#include "Window.h"
#include "Common.h"
#include "MemoryMapping.h"
/*
Some PAINTER ideas
------------------
Painters can paint to different types of targets
- currently only paints to a pixel buffer
Current implementation:
Each draw operation results in an immediate rasterization (calling Graphics functions)
Alternative ideas:
1) Use OpenGL - instead of rasterizing in software, rasterized in hardware
- this would involve turning the calls to graphics functions in to GL calls
- this may mean converting them in to vertex lists, shaders, texture uploads
- may require rethinking some APIs to make them more GPU friendly
2) The operations are collected to build a scene graph
- operations don't execute immediately, they are stored
- some operations might completely obscure others, so optimization is possible
- possibly the API changes to expose the scene tree to the caller
- the scene tree can be flushed, using OpenGL or current S/W implementation.
3) As with idea 2, when S/W rasterizing, this could be approached in 2 ways.
a) just walk the tree and call the graphics functions as usual
b i) iterate over all the pixels and for each pixel then walk the tree to
determine the final colour to output to the buffer. Obviously inefficient.
b ii) try to do something smarter. Each graphics function call usually has a
x, y, width and height, eg it operate on a rectangular region of the
output buffer. If the output buffer is treated like an quadtree, the
rasterization process could begin with descending the quadtree and working
out which operations will influence the current quad. This might just end
up being more expensive, but it might be able to save some overdraw in
certain cases. Imagine the buffer is first cleared, so normally every
pixel is draw to. Then some area has an image draw to it. Every pixel
in that area will be written to twice. Using the quadtree idea, this
gets optimized away if it knows the last fully opaque operation is drawing
the image, it can avoid clearing first. That could very easily be 1000s of
memory writes saved. In cases of blending, perhaps it can avoid writing
to the buffer and then reading the buffer back again, it might just read
and blend in a register between the opaque background and the thing
overlaid on top.
*/
extern int DecodePNG(std::vector<unsigned char>& out_image, unsigned long& image_width, unsigned long& image_height, const unsigned char* in_png, size_t in_size, bool convert_to_rgba32);
BEGIN_NAMESPACE
const char* Font::Helvetica = "Helvetica";
const char* Font::Times = "Times";
const char* Font::Courier = "Courier";
const char* Font::OldEnglish = "OldEnglish";
const char* Font::Monospace = "monospace";
const char* Font::Fantasy = "fantasy";
const char* Font::Cursive = "cursive";
//const char* Font::UIFont = "Segeo UI"; // Windows
const char* Font::UIFont = "San Francisco"; // macOS
struct PainterData
{
#if USE_RETINA
PixelBuffer m_targetCopy;
#endif
PixelBuffer* m_target;
Pen m_pen;
Brush m_brush;
Font m_font;
float m_scale;
};
struct PixmapData
{
PixelBuffer m_target;
std::vector<uint8_t> m_bits;
};
Pixmap::Pixmap(unsigned char* a_memoryBuffer, int a_width, int a_height, int a_depth)
{
m_data = new PixmapData;
m_data->m_target.m_pixels = (uint32_t*)a_memoryBuffer;
m_data->m_target.m_strideBytes = a_width * a_depth;
m_data->m_target.m_width = a_width;
m_data->m_target.m_height = a_height;
m_data->m_target.m_isRetina = false;
switch (a_depth)
{
case 2: m_data->m_target.m_format = PF_RGB565;
case 3: m_data->m_target.m_format = PF_RGB888;
case 4: m_data->m_target.m_format = PF_RGBA8888;
}
}
Pixmap::Pixmap(const char* a_fileName)
{
//static std::map<const char*,PixmapData> pixmapCache;
m_data = new PixmapData;
MemoryMappingData* mapping = MemoryMapping_Open(a_fileName);
// For 32bit builds, this code won't support input PNG files that are more than 4GiB, but neither can it support
// having a buffer as large as that for storing the pixels in to, so perhaps best just to use 64bit builds if dealing with massive images.
unsigned long w, h;
DecodePNG(m_data->m_bits, w, h, (uint8_t*)MemoryMapping_GetAddress(mapping), (size_t)MemoryMapping_GetSize(mapping), true);
m_data->m_target.m_width = w;
m_data->m_target.m_height = h;
MemoryMapping_Close(mapping);
/*
// need to swap red and blue channels for some reason
for (int i = 0; i < m_data->m_target.m_width * m_data->m_target.m_height; i++)
{
uint8_t red = m_data->m_bits[i*4+1];
m_data->m_bits[i*4+1] = m_data->m_bits[i*4+3];
m_data->m_bits[i*4+3] = red;
}
*/
//TODO: for OpenGL, making the width/height a power of two would be better
// need to check that. Possibly the power of two requirement might only
// be needed if texture wrapping is needed and its okay if not wrapping.
// Probably should carefully check the specs.
m_data->m_target.m_pixels = (uint32_t*)&m_data->m_bits[0];
m_data->m_target.m_strideBytes = m_data->m_target.m_width * sizeof(uint32_t);
m_data->m_target.m_format = PF_ARGB8888;
m_data->m_target.m_isRetina = false;
}
Pixmap::~Pixmap()
{
delete m_data;
}
const uint32_t* Pixmap::bits() const
{
return m_data->m_target.m_pixels;
}
uint32_t Pixmap::width() const
{
return m_data->m_target.m_width;
}
uint32_t Pixmap::height() const
{
return m_data->m_target.m_height;
}
PixelBuffer& Pixmap::targetBuffer()
{
return m_data->m_target;
}
PainterData* newPainterData()
{
PainterData* data = new PainterData;
data->m_target = 0;
data->m_pen.color = 0xffffffff;
data->m_brush.color = 0xff000000;
data->m_font.pointSize = 12;
data->m_font.family = "Arial";
data->m_scale = 1.0f;
return data;
}
Painter::Painter()
{
m_data = newPainterData();
}
Painter::Painter(PaintTargetInterface* a_window)
{
m_data = newPainterData();
m_data->m_scale = a_window->targetScale();
#if USE_RETINA
if (c_useRetina)
{
m_data->m_targetCopy = a_window->targetBuffer();
// auto w = dynamic_cast<Window*>(a_window);
if (m_data->m_targetCopy.m_isRetina)
{
m_data->m_targetCopy.m_width *= 2.0f;//c_retinaScale;
m_data->m_targetCopy.m_height *= 2.0f;//c_retinaScale;
}
//if (w == nullptr)
// m_data->m_targetCopy.m_isRetina = false;
m_data->m_target = &m_data->m_targetCopy;
}
else
#endif
{
m_data->m_target = &a_window->targetBuffer();
}
}
Painter::~Painter()
{
delete m_data;
}
void Painter::setPen(Pen a_pen)
{
m_data->m_pen = a_pen;
}
void Painter::setBrush(Brush a_brush)
{
m_data->m_brush = a_brush;
}
void Painter::setFont(Font a_font)
{
m_data->m_font = a_font;
}
void Painter::setPen(uint32_t a_color)
{
//m_data->m_penColor = a_color;
m_data->m_pen = Pen(a_color);
}
void Painter::setBrush(uint32_t a_color)
{
//m_data->m_brushColor = a_color;
m_data->m_brush = Brush(a_color);
}
void Painter::setFontFamily(const char* a_fontName)
{
m_data->m_font.family = a_fontName;
// m_data->m_fontFamily = a_fontName;
}
void Painter::setFontSize(uint32_t a_size)
{
m_data->m_font.pointSize = a_size;
// m_data->m_fontSize = a_size;
}
void Painter::drawText(int a_x, int a_y, const String& a_formattedUtf8String, int a_flags)
// void Painter::drawText(int a_x, int a_y, const char* a_formattedUtf8String, int a_flags)
{
int fontSize = m_data->m_font.pointSize.value();
int dummy;
scaleArguments(a_x, a_y, fontSize, dummy);
DrawText(m_data->m_target, m_data->m_pen.color.value(), m_data->m_font.family.value().c_str(), fontSize, a_x, a_y, a_formattedUtf8String, a_flags);
}
Size Painter::textExtents(const char* a_formattedUtf8String)
{
Size s;
GetTextExtents(m_data->m_target, m_data->m_font.family.value().c_str(), m_data->m_font.pointSize.value(), a_formattedUtf8String, s.m_width, s.m_height);
return s;
}
FontMetrics Painter::fontMetrics()
{
return GetFontMetrics();
}
GlyphMetrics Painter::glyphMetrics(uint32_t a_unicodeCharacter)
{
return GetGlyphMetrics(a_unicodeCharacter);
}
void Painter::reset()
{
/*
DrawRectangle(m_data->m_target, 0, 0, 0, m_data->m_target->m_width, m_data->m_target->m_height, false);
*/
#if USE_RETINA
if (c_useRetina)
{
DrawRectangle(m_data->m_target, 0, 0, 0, m_data->m_target->m_width / c_retinaScale, m_data->m_target->m_height / c_retinaScale, false);
}
else
#endif
DrawRectangle(m_data->m_target, 0, 0, 0, m_data->m_target->m_width, m_data->m_target->m_height, false);
}
void Painter::drawRectangle(int a_x, int a_y, int a_width, int a_height, bool a_alphaBlending)
{
scaleArguments(a_x, a_y, a_width, a_height);
if ( a_alphaBlending )
DrawRectangleAlpha(m_data->m_target, m_data->m_brush.color.value(), a_x, a_y, a_width, a_height);
else
DrawRectangle(m_data->m_target, m_data->m_brush.color.value(), a_x, a_y, a_width, a_height, true);
}
void Painter::drawFocusRectangle(int a_x, int a_y, int a_width, int a_height)
{
scaleArguments(a_x, a_y, a_width, a_height);
PixelBuffer* a_target = m_data->m_target;
uint32_t a_color = m_data->m_pen.color.value();
// This code draws a dotted line that alternates between on and off
// following around the edge of the given rectangle. It carefully
// handles the lines as they go around corners to keep the on-off pattern.
for (int i = 0; i < a_width; i++)
{
int j = 0;
int x = i + a_x;
int y = j + a_y;
if ( x >= 0 && x < a_target->m_width && y >= 0 && y < a_target->m_height ) {
uint32_t *dst = &(a_target->m_pixels[y*a_target->m_strideBytes/4 + x]);
*dst = a_color;
}
i++;
if (i == a_width)
break;
j = a_height - 1;
x = i + a_x - (a_height & 1);
y = j + a_y;
if ( x >= 0 && x < a_target->m_width && y >= 0 && y < a_target->m_height ) {
uint32_t *dst = &(a_target->m_pixels[y*a_target->m_strideBytes/4 + x]);
*dst = a_color;
}
}
for (int j = 2; j < a_height; j+=2) // TODO: handle cases where w or h are < 3
{
int i = 0;
int x = i + a_x;
int y = j + a_y;
if ( x >= 0 && x < a_target->m_width && y >= 0 && y < a_target->m_height ) {
uint32_t *dst = &(a_target->m_pixels[y*a_target->m_strideBytes/4 + x]);
*dst = a_color;
}
i = a_width - 1;
x = i + a_x;
y = j + a_y - 1 + (a_width & 1);
if ( x >= 0 && x < a_target->m_width && y >= 0 && y < a_target->m_height ) {
uint32_t *dst = &(a_target->m_pixels[y*a_target->m_strideBytes/4 + x]);
*dst = a_color;
}
}
}
void Painter::drawEllipse(int a_x, int a_y, int a_width, int a_height)
{
scaleArguments(a_x, a_y, a_width, a_height);
DrawEllipse(m_data->m_target, m_data->m_brush.color.value(), a_x, a_y, a_width, a_height, true);
}
void Painter::drawGradient(int a_x, int a_y, Gradient a_gradient, int a_width, int a_height)
{
scaleArguments(a_x, a_y, a_width, a_height);
#if USE_RETINA
if (c_useRetina)
{
//float scale = c_retinaScale;
float scale = c_retinaScale * m_data->m_scale;
switch (a_gradient.m_type)
{
case RADIAL_GRADIENT:
a_gradient.m_data.m_radial.m_centerX *= scale;
a_gradient.m_data.m_radial.m_centerY *= scale;
break;
case CONICAL_GRADIENT:
a_gradient.m_data.m_conic.m_centerX *= scale;
a_gradient.m_data.m_conic.m_centerY *= scale;
break;
case LINEAR_GRADIENT:
a_gradient.m_data.m_linear.m_x1 *= scale;
a_gradient.m_data.m_linear.m_y1 *= scale;
a_gradient.m_data.m_linear.m_x2 *= scale;
a_gradient.m_data.m_linear.m_y2 *= scale;
break;
}
}
#endif
DrawGradient(m_data->m_target, a_gradient, a_x, a_y, a_width, a_height);
}
void Painter::drawLine(int a_x, int a_y, int a_x2, int a_y2)
{
scaleArguments(a_x, a_y, a_x2, a_y2);
DrawLine(m_data->m_target, m_data->m_pen.color.value(), a_x, a_y, a_x2, a_y2, false);
}
void Painter::drawPixmap(int a_x, int a_y, const Pixmap& pixmap, int a_x1, int a_y1, int a_x2, int a_y2, bool a_alphaMask)
{
int dummy;
scaleArguments(a_x, a_y, dummy, dummy);
if (a_x2 == -1)
a_x2 = a_x1 + pixmap.width() - 1;
if (a_y2 == -1)
a_y2 = a_y1 + pixmap.height() - 1;
if ( a_alphaMask )
DrawPixels(m_data->m_target, (uint32_t*)pixmap.bits(), a_x, a_y, a_x2-a_x1+1, a_y2-a_y1+1, a_x1, a_y1, pixmap.width(), pixmap.height());
else
DrawPixelsAlpha(m_data->m_target, (uint32_t*)pixmap.bits(), a_x, a_y, a_x2-a_x1+1, a_y2-a_y1+1, a_x1, a_y1, pixmap.width(), pixmap.height());
}
void Painter::drawPixmapAlphaBlended(int a_x, int a_y, const Pixmap& pixmap, int a_x1, int a_y1, int a_x2, int a_y2, int a_alpha)
{
int dummy;
scaleArguments(a_x, a_y, dummy, dummy);
DrawPixelsAlphaBlended(m_data->m_target, (uint32_t*)pixmap.bits(), a_x1, a_y1, a_x2, a_y2, a_x, a_y, pixmap.width(), pixmap.height(), a_alpha);
}
void Painter::drawPixelBuffer(int a_x, int a_y, const uint8_t* a_pixels, int a_pixelsWidth, int a_pixelsHeight, int a_pixelsDepth)
{
int dummy;
scaleArguments(a_x, a_y, dummy, dummy);
DrawPixelsAlpha(m_data->m_target, (uint32_t*)a_pixels, a_x, a_y, a_pixelsWidth, a_pixelsHeight, 0, 0, a_pixelsWidth, a_pixelsHeight);
}
void Painter::scaleArguments(int& a_x, int& a_y, int& a_width, int& a_height)
{
#if USE_RETINA
if (c_useRetina)
{
//float scale = c_retinaScale;
float scale = c_retinaScale * m_data->m_scale;
a_x *= scale; a_y *= scale; a_width *= scale; a_height *= scale;
}
#endif
}
END_NAMESPACE