You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

621 lines
11 KiB

#include "Stroke.h"
#include <serializing/ObjectInputStream.h>
#include <serializing/ObjectOutputStream.h>
#include <i18n.h>
#include <cmath>
Stroke::Stroke()
: AudioElement(ELEMENT_STROKE)
{
XOJ_INIT_TYPE(Stroke);
}
Stroke::~Stroke()
{
XOJ_CHECK_TYPE(Stroke);
g_free(this->points);
this->points = NULL;
this->pointCount = 0;
this->pointAllocCount = 0;
XOJ_RELEASE_TYPE(Stroke);
}
/**
* Clone style attributes, but not the data (position, width etc.)
*/
void Stroke::applyStyleFrom(const Stroke* other)
{
setColor(other->getColor());
setToolType(other->getToolType());
setWidth(other->getWidth());
setFill(other->getFill());
setLineStyle(other->getLineStyle());
cloneAudioData(other);
}
Stroke* Stroke::cloneStroke() const
{
XOJ_CHECK_TYPE(Stroke);
Stroke* s = new Stroke();
s->applyStyleFrom(this);
s->allocPointSize(this->pointCount);
memcpy(s->points, this->points, this->pointCount * sizeof(Point));
s->pointCount = this->pointCount;
return s;
}
Element* Stroke::clone()
{
XOJ_CHECK_TYPE(Stroke);
return this->cloneStroke();
}
void Stroke::serialize(ObjectOutputStream& out)
{
XOJ_CHECK_TYPE(Stroke);
out.writeObject("Stroke");
serializeAudioElement(out);
out.writeDouble(this->width);
out.writeInt(this->toolType);
out.writeInt(fill);
out.writeData(this->points, this->pointCount, sizeof(Point));
this->lineStyle.serialize(out);
out.endObject();
}
void Stroke::readSerialized(ObjectInputStream& in)
{
XOJ_CHECK_TYPE(Stroke);
in.readObject("Stroke");
readSerializedAudioElement(in);
this->width = in.readDouble();
this->toolType = (StrokeTool) in.readInt();
this->fill = in.readInt();
g_free(this->points);
this->points = NULL;
this->pointCount = 0;
in.readData((void**) &this->points, &this->pointCount);
this->lineStyle.readSerialized(in);
in.endObject();
}
/**
* Option to fill the shape:
* -1: The shape is not filled
* 255: The shape is fully opaque filled
* ...
* 1: The shape is nearly fully transparent filled
*/
int Stroke::getFill() const
{
XOJ_CHECK_TYPE(Stroke);
return fill;
}
/**
* Option to fill the shape:
* -1: The shape is not filled
* 255: The shape is fully opaque filled
* ...
* 1: The shape is nearly fully transparent filled
*/
void Stroke::setFill(int fill)
{
XOJ_CHECK_TYPE(Stroke);
this->fill = fill;
}
void Stroke::setWidth(double width)
{
XOJ_CHECK_TYPE(Stroke);
this->width = width;
}
double Stroke::getWidth() const
{
XOJ_CHECK_TYPE(Stroke);
return this->width;
}
bool Stroke::isInSelection(ShapeContainer* container)
{
XOJ_CHECK_TYPE(Stroke);
for (int i = 0; i < this->pointCount; i++)
{
double px = this->points[i].x;
double py = this->points[i].y;
if (!container->contains(px, py))
{
return false;
}
}
return true;
}
void Stroke::setFirstPoint(double x, double y)
{
XOJ_CHECK_TYPE(Stroke);
if (this->pointCount > 0)
{
Point& p = this->points[0];
p.x = x;
p.y = y;
this->sizeCalculated = false;
}
}
void Stroke::setLastPoint(double x, double y)
{
XOJ_CHECK_TYPE(Stroke);
if (this->pointCount > 0)
{
Point& p = this->points[this->pointCount - 1];
p.x = x;
p.y = y;
this->sizeCalculated = false;
}
}
void Stroke::setLastPoint(Point p)
{
setLastPoint(p.x, p.y);
}
void Stroke::addPoint(Point p)
{
XOJ_CHECK_TYPE(Stroke);
if (this->pointCount >= this->pointAllocCount - 1)
{
this->allocPointSize(this->pointAllocCount + 100);
}
this->points[this->pointCount++] = p;
this->sizeCalculated = false;
}
void Stroke::allocPointSize(int size)
{
XOJ_CHECK_TYPE(Stroke);
this->pointAllocCount = size;
this->points = (Point*) g_realloc(this->points, this->pointAllocCount * sizeof(Point));
}
int Stroke::getPointCount() const
{
XOJ_CHECK_TYPE(Stroke);
return this->pointCount;
}
ArrayIterator<Point> Stroke::pointIterator() const
{
XOJ_CHECK_TYPE(Stroke);
return ArrayIterator<Point> (points, pointCount);
}
void Stroke::deletePointsFrom(int index)
{
XOJ_CHECK_TYPE(Stroke);
if (this->pointCount <= index)
{
return;
}
this->pointCount = index;
}
void Stroke::deletePoint(int index)
{
XOJ_CHECK_TYPE(Stroke);
if (this->pointCount <= index)
{
return;
}
for (int i = 0; i < this->pointCount; i++)
{
if (i >= index)
{
this->points[i] = this->points[i + 1];
}
}
this->pointCount--;
}
Point Stroke::getPoint(int index) const
{
XOJ_CHECK_TYPE(Stroke);
if (index < 0 || index >= pointCount)
{
g_warning("Stroke::getPoint(%i) out of bounds!", index);
return Point(0, 0, Point::NO_PRESSURE);
}
return points[index];
}
const Point* Stroke::getPoints() const
{
XOJ_CHECK_TYPE(Stroke);
return this->points;
}
void Stroke::freeUnusedPointItems()
{
XOJ_CHECK_TYPE(Stroke);
if (this->pointAllocCount == this->pointCount)
{
return;
}
this->pointAllocCount = this->pointCount + 1;
this->points = (Point*) g_realloc(this->points, this->pointAllocCount * sizeof(Point));
}
void Stroke::setToolType(StrokeTool type)
{
XOJ_CHECK_TYPE(Stroke);
this->toolType = type;
}
StrokeTool Stroke::getToolType() const
{
XOJ_CHECK_TYPE(Stroke);
return this->toolType;
}
void Stroke::setLineStyle(const LineStyle& style)
{
XOJ_CHECK_TYPE(Stroke);
this->lineStyle = style;
}
const LineStyle& Stroke::getLineStyle() const
{
XOJ_CHECK_TYPE(Stroke);
return this->lineStyle;
}
void Stroke::move(double dx, double dy)
{
XOJ_CHECK_TYPE(Stroke);
for (int i = 0; i < pointCount; i++)
{
points[i].x += dx;
points[i].y += dy;
}
this->sizeCalculated = false;
}
void Stroke::rotate(double x0, double y0, double xo, double yo, double th)
{
XOJ_CHECK_TYPE(Stroke);
for (int i = 0; i < this->pointCount; i++)
{
Point& p = this->points[i];
p.x -= x0; //move to origin
p.y -= y0;
double offset = 0.7; // __DBL_EPSILON__;
p.x -= xo-offset; //center to origin
p.y -= yo-offset;
double x1 = p.x * cos(th) - p.y * sin(th);
double y1 = p.y * cos(th) + p.x * sin(th);
p.x = x1;
p.y = y1;
p.x += x0; //restore the position
p.y += y0;
p.x += xo-offset; //center it
p.y += yo-offset;
}
//Width and Height will likely be changed after this operation
calcSize();
}
void Stroke::scale(double x0, double y0, double fx, double fy)
{
XOJ_CHECK_TYPE(Stroke);
double fz = sqrt(fx * fy);
for (int i = 0; i < this->pointCount; i++)
{
Point& p = this->points[i];
p.x -= x0;
p.x *= fx;
p.x += x0;
p.y -= y0;
p.y *= fy;
p.y += y0;
if (p.z != Point::NO_PRESSURE)
{
p.z *= fz;
}
}
this->width *= fz;
this->sizeCalculated = false;
}
bool Stroke::hasPressure() const
{
XOJ_CHECK_TYPE(Stroke);
if (this->pointCount > 0)
{
return this->points[0].z != Point::NO_PRESSURE;
}
return false;
}
double Stroke::getAvgPressure() const
{
XOJ_CHECK_TYPE(Stroke);
double summatory = 0;
for (int i = 0; i < this->pointCount; i++)
{
summatory += this->points[i].z;
}
return summatory / this->pointCount;
}
void Stroke::scalePressure(double factor)
{
XOJ_CHECK_TYPE(Stroke);
if (!hasPressure())
{
return;
}
for (int i = 0; i < this->pointCount; i++)
{
this->points[i].z *= factor;
}
}
void Stroke::clearPressure()
{
XOJ_CHECK_TYPE(Stroke);
for (int i = 0; i < this->pointCount; i++)
{
this->points[i].z = Point::NO_PRESSURE;
}
}
void Stroke::setLastPressure(double pressure)
{
XOJ_CHECK_TYPE(Stroke);
if (this->pointCount > 0)
{
this->points[this->pointCount - 1].z = pressure;
}
}
void Stroke::setPressure(const vector<double>& pressure)
{
XOJ_CHECK_TYPE(Stroke);
// The last pressure is not used - as there is no line drawn from this point
if (this->pointCount - 1 > (int)pressure.size())
{
g_warning("invalid pressure point count: %i, expected %i", (int)pressure.size(), (int)this->pointCount - 1);
return;
}
for (int i = 0; i < this->pointCount && i < (int)pressure.size(); i++)
{
this->points[i].z = pressure[i];
}
}
/**
* split index is the split point, minimimum is 1 NOT 0
*/
bool Stroke::intersects(double x, double y, double halfEraserSize)
{
XOJ_CHECK_TYPE(Stroke);
return intersects(x, y, halfEraserSize, nullptr);
}
/**
* split index is the split point, minimimum is 1 NOT 0
*/
bool Stroke::intersects(double x, double y, double halfEraserSize, double* gap)
{
XOJ_CHECK_TYPE(Stroke);
if (this->pointCount < 1)
{
return false;
}
double x1 = x - halfEraserSize;
double x2 = x + halfEraserSize;
double y1 = y - halfEraserSize;
double y2 = y + halfEraserSize;
double lastX = points[0].x;
double lastY = points[0].y;
for (int i = 1; i < pointCount; i++)
{
double px = points[i].x;
double py = points[i].y;
if (px >= x1 && py >= y1 && px <= x2 && py <= y2)
{
if (gap)
{
*gap = 0;
}
return true;
}
double len = hypot(px - lastX, py - lastY);
if (len >= halfEraserSize)
{
/**
* The normale to a vector, the padding to a point
*/
double p = ABS((x - lastX) * (lastY - py) + (y - lastY) * (px - lastX)) / hypot(lastX - x, lastY - y);
// The space to the line is in the range, but it can also be parallel
// and not enough close, so calculate a "circle" with the center on the
// center of the line
if (p <= halfEraserSize)
{
double centerX = (lastX + x) / 2;
double centerY = (lastY + y) / 2;
double distance = hypot(x - centerX, y - centerY);
// we should calculate the length of the line within the rectangle, to find out
// the distance from the border to the point, but the stroken are not rectangular
// so we can do it simpler
distance -= hypot((x2 - x1) / 2, (y2 - y1) / 2);
if (distance <= (len / 2) + 0.1)
{
if (gap)
{
*gap = distance;
}
return true;
}
}
}
lastX = px;
lastY = py;
}
return false;
}
/**
* Updates the size
* The size is needed to only redraw the requested part instead of redrawing
* the whole page (performance reason).
* Also used for Selected Bounding box.
*/
void Stroke::calcSize()
{
XOJ_CHECK_TYPE(Stroke);
if (this->pointCount == 0)
{
Element::x = 0;
Element::y = 0;
// The size of the rectangle, not the size of the pen!
Element::width = 0;
Element::height = 0;
}
double minX = DBL_MAX;
double maxX = DBL_MIN;
double minY = DBL_MAX;
double maxY = DBL_MIN;
bool hasPressure = points[0].z != Point::NO_PRESSURE;
double halfThick = this->width / 2.0; // accommodate for pen width
for (int i = 0; i < this->pointCount; i++)
{
if (hasPressure) halfThick = points[i].z / 2.0;
minX = std::min(minX, points[i].x - halfThick);
minY = std::min(minY, points[i].y - halfThick);
maxX = std::max(maxX, points[i].x + halfThick);
maxY = std::max(maxY, points[i].y + halfThick);
}
Element::x = minX - 2;
Element::y = minY - 2;
Element::width = maxX - minX + 4;
Element::height = maxY - minY + 4;
}
EraseableStroke* Stroke::getEraseable()
{
XOJ_CHECK_TYPE(Stroke);
return this->eraseable;
}
void Stroke::setEraseable(EraseableStroke* eraseable)
{
XOJ_CHECK_TYPE(Stroke);
this->eraseable = eraseable;
}
void Stroke::debugPrint()
{
XOJ_CHECK_TYPE(Stroke);
g_message("%s", FC(FORMAT_STR("Stroke {1} / hasPressure() = {2}") % (uint64_t) this % this->hasPressure()));
for (int i = 0; i < this->pointCount; i++)
{
Point p = this->points[i];
g_message("%lf / %lf", p.x, p.y);
}
g_message("\n");
}