/*
* File: draw.cc - Draw commands
* Project: GED - bitmap editor (ICP)
* Author: Kamil Dudka, xdudka00
* Team: xdudka00, xfilak01, xhefka00, xhradi08
* Created: 2006-04-11
*/
#include <iostream>
#include <sstream>
#include <cstring>
#include <ft2build.h>
#include FT_FREETYPE_H
#include "global.h"
#include "gedconf.h"
#include "draw.h"
using GlobalH::swap;
namespace {
const char* toolLabels [iDrawToolCnt] = {
"",
"Brush",
"Rubber",
"Line",
"Box",
"Circle",
"Ellipse",
"Text",
"Select"
};
// Read Pixel from stream
std::istream &operator>> (std::istream &s, Pixel &color) {
int i;
s >> i; color.red = i;
s >> i; color.green = i;
s >> i; color.blue = i;
s >> i; color.alpha = i;
return s;
}
// Write Pixel to stream
std::ostream &operator<< (std::ostream &s, const Pixel &color) {
s << " " << static_cast<int> (color.red);
s << " " << static_cast<int> (color.green);
s << " " << static_cast<int> (color.blue);
s << " " << static_cast<int> (color.alpha);
return s;
}
// Read Point from stream
std::istream &operator>> (std::istream &s, Point &pos) {
s >> pos.x >> pos.y;
return s;
}
// Write Point to stream
std::ostream &operator<< (std::ostream &s, const Point &pos) {
s << " " << pos.x << " " << pos.y;
return s;
}
// Read Rect from stream
std::istream &operator>> (std::istream &s, Rect &rect) {
s >> rect.at.x >> rect.at.y >> rect.size.width >> rect.size.height;
return s;
}
// Write Rect to stream
std::ostream &operator<< (std::ostream &s, const Rect &rect) {
s << " " << rect.at.x << " " << rect.at.y;
s << " " << rect.size.width << " " << rect.size.height;
return s;
}
// Weight awerage of two colors
inline Pixel WeightAverage (Pixel a, Pixel b, unsigned char ratio) {
const int aScale= ratio;
const int bScale= UCHAR_MAX - ratio;
const unsigned long red= a.red*aScale + b.red*bScale;
const unsigned long green= a.green*aScale + b.green*bScale;
const unsigned long blue= a.blue*aScale + b.blue*bScale;
const unsigned long alpha= a.alpha*aScale + b.alpha*bScale;
Pixel tmp= {
red / UCHAR_MAX,
green / UCHAR_MAX,
blue / UCHAR_MAX,
alpha / UCHAR_MAX
};
return tmp;
}
}
const char *const BrushCmd::szCmdName = toolLabels [TOOL_BRUSH];
const char *const LineCmd::szCmdName = toolLabels [TOOL_LINE];
const char *const RectCmd::szCmdName = toolLabels [TOOL_RECT];
const char *const CircleCmd::szCmdName = toolLabels [TOOL_CIRCLE];
const char *const EllipseCmd::szCmdName = toolLabels [TOOL_ELLIPSE];
const char *const TextCmd::szCmdName = toolLabels [TOOL_TEXT];
const char *const SelectCmd::szCmdName = toolLabels [TOOL_SELECT];
const char *const GrayScaleCmd::szCmdName = "Grayscale";
const char *const InvertCmd::szCmdName = "Invert";
/*
* Return tool "et" label
*/
const char *DrawContext::toolLabel (EDrawTool et)
{
return toolLabels [et];
}
/*
* Return tool "et" id from label "text"
*/
EDrawTool DrawContext::toolByLabel (const char *text)
{
for (int et= TOOL_NOTOOL; et<iDrawToolCnt; et++)
if (0==std::strcmp (text, toolLabels [et]))
return static_cast<EDrawTool>(et);
return TOOL_NOTOOL;
}
/*
* Construct brush command
*/
BrushCmd::BrushCmd (FrameBuffer *pFB, const DrawContext &dc, bool bBack):
SlowUndoSelectCmd (pFB)
{
if (bBack) {
_iBrushWidth = dc.bgBrushWidth;
_brushColor = dc.bgColor;
} else {
_iBrushWidth = dc.fgBrushWidth;
_brushColor = dc.fgColor;
}
}
/*
* Brush command export
*/
std::string BrushCmd::args ()
{
std::ostringstream s;
s << _brushColor;
for (
std::set<int>::iterator i = _pixSet.begin ();
i != _pixSet.end ();
i ++)
{
Point pos (
(*i) % _fb.size().width,
(*i) / _fb.size().width);
s << pos;
}
return s.str();
}
/*
* Brush command import
*/
BrushCmd::BrushCmd (FrameBuffer *pFB, const std::string &args):
SlowUndoSelectCmd (pFB), _iBrushWidth (1)
{
std::istringstream s (args);
s >> _brushColor;
while (s) {
Point pixel;
s >> pixel;
add (pixel);
}
}
/*
* Add pixel into brush command pixmap
*/
void BrushCmd::add (Point pos)
{
for (int y = pos.y; y < pos.y+_iBrushWidth; y++)
for (int x = pos.x; x < pos.x+_iBrushWidth; x++) {
// Index to bitmap array
int curr = y*_fb.size().width + x;
// Add pixel to map
_pixSet.insert (curr);
}
}
namespace {
/*
* Pixel draw encapsulation for BrushCmd::exec()
*/
class BrushPixel {
FrameBuffer &_fb;
Pixel _color;
public:
BrushPixel (FrameBuffer &fb, Pixel &color): _fb (fb), _color (color) { }
void operator() (const int iPixel) const {
const int iWidth= _fb.size().width;
const Point pos (
iPixel % iWidth,
iPixel / iWidth);
_fb.setPixel (pos, _color);
}
};
}
/*
* Execute brush command
*/
void BrushCmd::exec ()
{
// Save Undo
SlowUndoSelectCmd::exec ();
// Draw all pixels
BrushPixel pen (_fb, _brushColor);
std::for_each (_pixSet.begin(), _pixSet.end(), pen);
}
/*
* Construct placement command (abstract class)
*/
PlacementCmd::PlacementCmd (FrameBuffer *pFB, Rect &placement, DrawContext &dc, bool bBack):
SlowUndoSelectCmd (pFB), _place (placement)
{
if (bBack) {
_iBrushWidth = dc.bgBrushWidth;
_brushColor = dc.bgColor;
} else {
_iBrushWidth = dc.fgBrushWidth;
_brushColor = dc.fgColor;
}
}
/*
* Placement command export (abstract class)
*/
std::string PlacementCmd::args ()
{
std::ostringstream s;
s << _iBrushWidth << _brushColor << _place;
return s.str();
}
/*
* Placement command import (abstract class)
*/
PlacementCmd::PlacementCmd (FrameBuffer *pFB, const std::string &args):
SlowUndoSelectCmd (pFB)
{
std::istringstream s (args);
s >> _iBrushWidth >> _brushColor >> _place;
}
/*
* Imperess selected brush at position "pos"
*/
void PlacementCmd::impressBrush (Point pos)
{
// Brush metrics
const int min = - (_iBrushWidth >> 1);
const int max = min + _iBrushWidth;
const int RR = max * max;
// Position correction
pos -= Point (min, min);
// impress circle brush
for (int y=min; y<max; y++)
for (int x=min; x<max; x++)
if ( (x*x + y*y) <= RR)
_fb.setPixel (pos + Point (x, y), _brushColor);
}
/*
* Execute line command
*/
void LineCmd::exec ()
{
// Save undo
SlowUndoSelectCmd::exec ();
// Get line metrics
int x1 = _place.at.x;
int y1 = _place.at.y;
int x2 = x1 + _place.size.width;
int y2 = y1 + _place.size.height;
int dx = abs (_place.size.width);
int dy = abs (_place.size.height);
// Swap axis if needed
bool bInvAxis = dy>dx;
if (bInvAxis) {
swap (x1, y1);
swap (x2, y2);
swap (dx, dy);
}
// Swap points acoording to axis x
if (x1>x2) {
swap (x1, x2);
swap (y1, y2);
}
// Step in axis y
int iStepY = (y1>y2) ? (-1):1;
// Bresenham algorithm
const int k1 = 2*dy;
const int k2 = k1 - 2*dx;
int P=2*dy - dx;
int x,y;
for (x=x1, y=y1; x<=x2; x++) {
if (P<0)
P += k1;
else {
y += iStepY;
P += k2;
}
if (bInvAxis)
impressBrush (Point (y, x));
else
impressBrush (Point (x, y));
}
}
/*
* Execute rectanle command
*/
void RectCmd::exec ()
{
// Save undo
SlowUndoSelectCmd::exec ();
// Draw rectangle
const int xMax = _place.at.x + _place.size.width;
const int yMax = _place.at.y + _place.size.height;
for (int x=_place.at.x; x< xMax; x++)
impressBrush (Point (x, _place.at.y));
for (int y=_place.at.y; y< yMax; y++)
impressBrush (Point (_place.at.x, y));
for (int x=_place.at.x; x< xMax; x++)
impressBrush (Point (x, yMax));
for (int y=_place.at.y; y< yMax; y++)
impressBrush (Point (xMax, y));
}
/*
* Execute circle command
*/
void CircleCmd::exec ()
{
// Save undo
SlowUndoSelectCmd::exec ();
// Draw circle (8x symetric) using Midpoint Algorithm
int x= 0;
int y= _iRadius;
int d= 1-_iRadius;
drawCirclePoints (x, y);
while (y > x) {
x++;
if (d < 0) {
d += 2*x+1;
} else {
y --;
d += 2*(x-y+1);
}
drawCirclePoints (x, y);
}
}
/*
* Draw 8 points of circle
*/
void CircleCmd::drawCirclePoints (int x, int y)
{
impressBrush (Point (_iCenterX - y, _iCenterY + x));
impressBrush (Point (_iCenterX + y, _iCenterY + x));
impressBrush (Point (_iCenterX - x, _iCenterY + y));
impressBrush (Point (_iCenterX + x, _iCenterY + y));
impressBrush (Point (_iCenterX - x, _iCenterY - y));
impressBrush (Point (_iCenterX + x, _iCenterY - y));
impressBrush (Point (_iCenterX - y, _iCenterY - x));
impressBrush (Point (_iCenterX + y, _iCenterY - x));
}
/*
* Execute ellipse command
*/
void EllipseCmd::exec ()
{
// Save undo
SlowUndoSelectCmd::exec ();
// Midpoint algorithm
int x= 0;
int y= _iHalfAxisB;
const int AA= _iHalfAxisA * _iHalfAxisA;
const int BB= _iHalfAxisB * _iHalfAxisB;
int P= BB - AA*_iHalfAxisB + AA/4;
while (AA*y > BB*x) {
drawEllipsePoints (x, y);
if (P < 0) {
P += BB*(2*x+3);
x++;
} else {
P += BB*(2*x+3) + AA*(2-2*y);
x++;
y--;
}
}
P = BB*(x*x + x) + BB/4 + AA*(y-1)*(y-1) - AA*BB;
while (y > 0) {
drawEllipsePoints (x, y);
if (P < 0) {
P += BB*(2*x+2) + AA*(3-2*y);
x++;
y--;
} else {
P += AA*(3-2*y);
y--;
}
}
}
/*
* Draw 4 points of ellipse
*/
void EllipseCmd::drawEllipsePoints (int x, int y)
{
impressBrush (Point (_iCenterX - x, _iCenterY + y));
impressBrush (Point (_iCenterX + x, _iCenterY + y));
impressBrush (Point (_iCenterX - x, _iCenterY - y));
impressBrush (Point (_iCenterX + x, _iCenterY - y));
}
/*
* Construct text command
*/
TextCmd::TextCmd (FrameBuffer *pFB, DrawContext dc, Point position, const char *szText):
SlowUndoSelectCmd (pFB),
_fgColor (dc.fgColor), _bgColor (dc.bgColor),
_pos (position), _strText (szText)
{
// Load font properties from configuration file
init ();
// Apply brush width
_iSize *= dc.fgBrushWidth;
}
/*
* Load font properties
*/
void TextCmd::init ()
{
// Read data from configuration file
std::string dpi;
std::string size;
GedConf::ptr()->getFont (_fontFile, dpi, size);
// Save dpi
std::istringstream s1 (dpi);
s1 >> _iDpi;
// Save base font size
std::istringstream s2 (size);
s2 >> _iSize;
}
/*
* Text command export
*/
std::string TextCmd::args ()
{
std::ostringstream s;
s << _iSize << _fgColor << _bgColor << _pos << ' ' << _strText;
return s.str();
}
/*
* Text command import
*/
TextCmd::TextCmd (FrameBuffer *pFB, const std::string &args):
SlowUndoSelectCmd (pFB)
{
// Load font properties from configuration file
init();
// Read metrics
std::istringstream s (args);
s >> _iSize >> _fgColor >> _bgColor >> _pos;
// Read text string
getline (s, _strText);
std::string::iterator b= _strText.begin ();
if (b!= _strText.end() && *b==' ')
_strText.erase (b);
}
/*
* Execute text command
*/
void TextCmd::exec ()
{
// Save undo
SlowUndoSelectCmd::exec ();
// FT_Library encapsulation
class FreeTypeObject {
FT_Library _ft;
public:
FreeTypeObject () {
if (0!= FT_Init_FreeType (&_ft))
throw ErrFreeType ();
}
~FreeTypeObject () {
FT_Done_FreeType (_ft);
}
operator FT_Library&() { return _ft; }
} lib;
// FT_Face encapsulation
class FaceObject {
FT_Face _face;
public:
FaceObject (FT_Library &lib, const char *szFontFile) {
if (0!= FT_New_Face (lib, szFontFile, 0, &_face))
throw ErrFreeType ();
}
~FaceObject () {
FT_Done_Face (_face);
}
operator FT_Face&() { return _face; }
} face (lib, _fontFile.c_str ());
// Set font metric
if (0!= FT_Set_Char_Size (face, _iSize << 6, 0, _iDpi, 0))
throw ErrFreeType ();
// FT variables
FT_GlyphSlot slot= FT_Face(face)->glyph;
FT_Bitmap &bitmap= slot->bitmap;
FT_Vector pen;
pen.x = _pos.x << 6;
pen.y = (_fb.size().height - _pos.y) << 6;
// Draw text string
for (std::string::iterator i=_strText.begin(); i!= _strText.end(); i++) {
// Load glyph
FT_Set_Transform (face, 0, &pen);
if (0!= FT_Load_Char (face, *i, FT_LOAD_RENDER))
throw ErrFreeType ();
// Draw glyph
const Point offset (slot->bitmap_left, _fb.size().height - slot->bitmap_top);
for (int y=0; y< bitmap.rows; y++)
for (int x=0; x< bitmap.width; x++) {
unsigned char value= bitmap.buffer [y*bitmap.width + x];
if (0== value)
continue;
// Draw pixel
Point pos= Point (x, y) + offset;
Pixel color= WeightAverage (_fgColor, _bgColor, value);
_fb.setPixel (pos, color);
}
// Move to next position
pen.x += slot->advance.x;
pen.y += slot->advance.y;
}
}
/*
* Construct select command
*/
SelectCmd::SelectCmd (FrameBuffer *pFB, const Rect &sel):
Cmd (pFB), _newSel (sel)
{
}
/*
* Construct select-all command
*/
SelectCmd::SelectCmd (FrameBuffer *pFB):
Cmd (pFB)
{
_newSel.at = Point (0, 0);
_newSel.size = _fb.size ();
}
/*
* Select command export
*/
std::string SelectCmd::args ()
{
std::ostringstream s;
s << _newSel;
return s.str();
}
/*
* Select command import
*/
SelectCmd::SelectCmd (FrameBuffer *pFB, const std::string &args):
Cmd (pFB)
{
std::istringstream s (args);
s >> _newSel;
}
/*
* Execute select command
*/
void SelectCmd::exec ()
{
_oldSel = _fb.select();
_fb.select (_newSel);
}
/*
* Revert select command
*/
void SelectCmd::unExec ()
{
_fb.select (_oldSel);
}
/*
* Execute filter "Grayscale"
*/
void GrayScaleCmd::exec ()
{
// Save undo informations
SlowUndoSelectCmd::exec();
// Selected area range
const int xMin= _fb.select().at.x;
const int yMin= _fb.select().at.y;
const int xMax= xMin + _fb.select().size.width;
const int yMax= yMin + _fb.select().size.height;
for (int y= yMin; y< yMax; y++)
for (int x= xMin; x< xMax; x++) {
Pixel &pix= _fb [y][x];
// Weight average
const int iIntensity= static_cast<int> (
0.299* pix.red +
0.587* pix.green +
0.114* pix.blue);
pix.red= pix.green= pix.blue= iIntensity;
}
}
/*
* Execute filter "Invert"
*/
void InvertCmd::exec ()
{
// Save undo informations
SlowUndoSelectCmd::exec();
// Selected area range
const int xMin= _fb.select().at.x;
const int yMin= _fb.select().at.y;
const int xMax= xMin + _fb.select().size.width;
const int yMax= yMin + _fb.select().size.height;
for (int y= yMin; y< yMax; y++)
for (int x= xMin; x< xMax; x++) {
Pixel &pix= _fb [y][x];
// Invert all colors
pix.red= UCHAR_MAX - pix.red;
pix.green= UCHAR_MAX - pix.green;
pix.blue= UCHAR_MAX - pix.blue;
}
}