Newer
Older
Import / research / ui / toolkit / src / Painter.cpp
#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