Annotation(PdfName.STAMP) missed after flattened by itextsharp5.5.13.1.
I have two pdfs. One does work, the other does not work.
Any ideas will be appreciated.
The code is following
string outFile = inputFile + "_f.pdf";
using (PdfReader pdfReader = new PdfReader(inputFileName))
{
PdfStamper pdfStamper = new PdfStamper(pdfReader, new FileStream(outFile, FileMode.Create, FileAccess.Write, FileShare.None))
//pdfStamper.FormFlattening = true;
//pdfStamper.FreeTextFlattening = true;
pdfStamper.AnnotationFlattening = true;
//pdfStamper.AcroFields.GenerateAppearances = true;
}
The cause is a bug in iTextSharp, annotation flattening does not correctly calculate the position for the flattened annotation if its bounding box does not use the origin as lower left corner.
If you look at the code of PdfStamperImp.FlattenAnnotations(bool), you'll quickly realize that the calculations in the if (app != null) block only make sense if the bounding box is located at the origin or no scaling is necessary for fitting the appearance bounding box into the annotation rectangle.
(As often the lower left corner of the bounding box is the origin, this does not pop up often.)
Thus, for flattening such annotations you have to use a different method for flattening, e.g. like this:
using (PdfReader pdfReader = new PdfReader(inputFileName))
{
PdfStamper pdfStamper = new PdfStamper(pdfReader, new FileStream(outFile, FileMode.Create, FileAccess.Write, FileShare.None));
ImprovedAnnotationFlattening(pdfStamper);
pdfStamper.Close();
}
with these helper methods:
void ImprovedAnnotationFlattening(PdfStamper pdfStamper)
{
double[] DEFAULT_MATRIX = { 1, 0, 0, 1, 0, 0 };
PdfReader reader = pdfStamper.Reader;
for (int page = 1; page <= reader.NumberOfPages; ++page)
{
PdfDictionary pageDic = reader.GetPageN(page);
PdfArray annots = pageDic.GetAsArray(PdfName.ANNOTS);
if (annots == null)
continue;
for (int idx = 0; idx < annots.Size; ++idx)
{
PdfObject annoto = annots.GetDirectObject(idx);
if (!(annoto is PdfDictionary))
continue;
PdfDictionary annDic = (PdfDictionary)annoto;
PdfNumber ff = annDic.GetAsNumber(PdfName.F);
int flags = ff != null ? ff.IntValue : 0;
if ((flags & PdfFormField.FLAGS_PRINT) == 0 || (flags & PdfFormField.FLAGS_HIDDEN) != 0)
continue;
PdfObject obj1 = annDic.Get(PdfName.AP);
if (obj1 == null)
continue;
PdfDictionary appDic = obj1 is PdfIndirectReference
? (PdfDictionary)PdfReader.GetPdfObject(obj1)
: (PdfDictionary)obj1;
PdfObject obj = appDic.Get(PdfName.N);
PdfStream objDict = appDic.GetAsStream(PdfName.N);
if (objDict != null)
{
Rectangle rect = PdfReader.GetNormalizedRectangle(annDic.GetAsArray(PdfName.RECT));
Rectangle bbox = PdfReader.GetNormalizedRectangle(objDict.GetAsArray(PdfName.BBOX));
PdfContentByte cb = pdfStamper.GetOverContent(page);
cb.SetLiteral("Q ");
PdfArray matrixArray = objDict.GetAsArray(PdfName.MATRIX);
double[] matrix = matrixArray != null ? matrixArray.AsDoubleArray() : DEFAULT_MATRIX;
AffineTransform transform = new AffineTransform(matrix);
double[] bboxCorners = { bbox.Left, bbox.Bottom, bbox.Right, bbox.Bottom, bbox.Right, bbox.Top, bbox.Left, bbox.Top };
transform.Transform(bboxCorners, 0, bboxCorners, 0, 4);
double minX = Min(bboxCorners, 0, 2);
double maxX = Max(bboxCorners, 0, 2);
double minY = Min(bboxCorners, 1, 2);
double maxY = Max(bboxCorners, 1, 2);
transform.preConcatenate(AffineTransform.GetTranslateInstance(-minX, -minY));
transform.preConcatenate(AffineTransform.GetScaleInstance(rect.Width/(maxX-minX), rect.Height/(maxY-minY)));
transform.preConcatenate(AffineTransform.GetTranslateInstance(rect.Left, rect.Bottom));
transform.GetMatrix(matrix);
cb.AddFormXObj(objDict, GenerateName(), matrix[0], matrix[1], matrix[2], matrix[3], matrix[4], matrix[5]);
cb.SetLiteral("q ");
annots.Remove(idx);
--idx;
}
}
}
}
double Min(double[] array, int start, int step)
{
double result = array[start];
for (int i = start + step; i < array.Length; i+=step)
{
result = Math.Min(result, array[i]);
}
return result;
}
double Max(double[] array, int start, int step)
{
double result = array[start];
for (int i = start + step; i < array.Length; i += step)
{
result = Math.Max(result, array[i]);
}
return result;
}
PdfName GenerateName()
{
PdfName name = new PdfName("XXX" + formXObjectsCounter);
++formXObjectsCounter;
return name;
}
int formXObjectsCounter = 4711;
Beware: I just wrote these methods (copying as much as possible from the original flattening code) and only tested with your example files. Some border conditions might still have to be considered for general use. In particular I did not do all relevant null or 0 tests. Also I did not attempt to support proper tagging.
I have an application that generates PDFs using the MigraDoc framework, however I have a requirement to add in a text driven watermark. I have found some examples of this being done using PDF Sharp here, however I just cant seem t be able to figure out how this will integrate with my Migradoc Document() object I am rendering.
I have the following code:
public byte[] render()
{
PdfDocument document = new PdfDocument();
CreateWaterMarks(document);
// *****************************
PdfDocumentRenderer renderer = new PdfDocumentRenderer(true);
renderer.Document = this.document;
renderer.RenderDocument();
byte[] pdfContents = null;
using (MemoryStream stream = new MemoryStream())
{
renderer.PdfDocument.Save(stream, true);
pdfContents = stream.ToArray();
}
return pdfContents;
}
This method is what is called to render the MigraDoc document and pass it out as a byte array. The second line of code in here calls the following method which is not doing what I am looking for:
void CreateWaterMarks(PdfDocument document)
{
PdfPage page = document.AddPage();
Document doc = this.document;
MigraDoc.Rendering.DocumentRenderer docRenderer = new DocumentRenderer(doc);
docRenderer.PrepareDocument();
XRect A4Rect = new XRect(0, 0, pageActiveWidth, pageActiveHeight);
int pageCount = docRenderer.FormattedDocument.PageCount;
for (int idx = 0; idx < pageCount; idx++)
{
XFont font = new XFont("Verdana", 13, XFontStyle.Bold);
XGraphics gfx = XGraphics.FromPdfPage(page, XGraphicsPdfPageOptions.Prepend);
XSize size = gfx.MeasureString("Watermark", font);
gfx.TranslateTransform(pageActiveWidth / 2, pageActiveHeight / 2);
gfx.RotateTransform(-Math.Atan(pageActiveHeight / pageActiveWidth) * 180 / Math.PI);
gfx.TranslateTransform(-pageActiveWidth / 2, -pageActiveHeight / 2);
XStringFormat format = new XStringFormat();
format.Alignment = XStringAlignment.Near;
format.LineAlignment = XLineAlignment.Near;
XBrush brush = new XSolidBrush(XColor.FromArgb(128, 255, 0, 0));
gfx.DrawString("Watermark", font, brush, new XPoint((pageActiveWidth - size.Width) / 2, (pageActiveHeight - size.Height) / 2), format);
docRenderer.RenderPage(gfx, idx + 1);
}
}
I was hoping that this would magically make these PDFSharp watermarks appear but alas I get nothing!
I have this working using the following code:
public byte[] render()
{
PdfDocumentRenderer renderer = new PdfDocumentRenderer(true);
renderer.Document = this.document;
renderer.RenderDocument();
renderer.PrepareRenderPages();
CreateWatermarks(renderer);
byte[] pdfContents = null;
using (MemoryStream stream = new MemoryStream())
{
renderer.PdfDocument.Save(stream, true);
pdfContents = stream.ToArray();
}
return pdfContents;
}
private void CreateWatermarks(PdfDocumentRenderer renderer)
{
int pages = renderer.DocumentRenderer.FormattedDocument.PageCount;
for (int i = 0; i < pages; ++i)
{
var page = renderer.PdfDocument.Pages[i];
XFont font = new XFont("Verdana", 27, XFontStyle.Bold);
XGraphics gfx = XGraphics.FromPdfPage(page, XGraphicsPdfPageOptions.Prepend);
XSize size = gfx.MeasureString("Watermark", font);
gfx.TranslateTransform(pageActiveWidth / 2, pageActiveHeight / 2);
gfx.RotateTransform(-Math.Atan(pageActiveHeight / pageActiveWidth) * 180 / Math.PI);
gfx.TranslateTransform(-pageActiveWidth / 2, -pageActiveHeight / 2);
XStringFormat format = new XStringFormat();
format.Alignment = XStringAlignment.Near;
format.LineAlignment = XLineAlignment.Near;
XBrush brush = new XSolidBrush(XColor.FromArgb(128, 255, 0, 0));
gfx.DrawString("Watermark", font, brush, new XPoint((pageActiveWidth - size.Width) / 2, (pageActiveHeight - size.Height) / 2), format);
}
}
I want to save a depth image that from frame buffer render result.
1, I create a stage buffer used to save image data.
2, use vkCmdCopyImageToBuffer copy depth image to stage buffer.
3, use vkMapMemory map this stage buffer memory to host memory.
4, read host memory and write depth data to a file.
but always got an error depth image. I don't know where have wrong.
application window output.
bug depth image file.
(source file)
save depth image function:
VkDeviceSize size = WIDTH * HEIGHT * 4;
VkBuffer dstBuffer;
VkDeviceMemory dstMemory;
createBuffer(
size,
VK_BUFFER_USAGE_TRANSFER_DST_BIT,
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
dstBuffer,
dstMemory);
VkCommandBuffer copyCmd = beginSingleTimeCommands();
// depth format -> VK_FORMAT_D32_SFLOAT_S8_UINT
VkBufferImageCopy region = {};
region.bufferOffset = 0;
region.bufferImageHeight = 0;
region.bufferRowLength = 0;
region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT;
region.imageSubresource.mipLevel = 0;
region.imageSubresource.baseArrayLayer = 0;
region.imageSubresource.layerCount = 1;
region.imageOffset = VkOffset3D{ 0, 0, 0 };
region.imageExtent = VkExtent3D{ swapChainExtent.width, swapChainExtent.height, 1};
vkCmdCopyImageToBuffer(
copyCmd,
depthImage, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
dstBuffer,
1,
®ion
);
endSingleTimeCommands(copyCmd);
// Map image memory so we can start copying from it
void *data;
vkMapMemory(device, dstMemory, 0, size, 0, &data);
std::ofstream file(path, std::ios::out | std::ios::binary);
// ppm header
file << "P6\n" << WIDTH << "\n" << HEIGHT << "\n" << 255 << "\n";
float *row = (float*)data;
auto size_v = WIDTH * HEIGHT;
for (uint32_t y = 0; y < size_v; y++) {
file.write((char*)row + 1, 1);
file.write((char*)row + 1, 1);
file.write((char*)row + 1, 1);
row++;
}
file.close();
// Clean up resources
vkUnmapMemory(device, dstMemory);
vkFreeMemory(device, dstMemory, nullptr);
vkDestroyBuffer(device, dstBuffer, nullptr);
hope someone drag me out. thanks!
Assuming you've done all the transfer work correctly, your mapped data is basically an array of floats. This is reflected in your code by this line:
float *row = (float*)data;
However, when you actually write out the file you're treating the data like bytes...
file.write((char*)row + 1, 1);
So you're writing out 8 bytes of a 32 bit float. What you need is some function to convert from the float to a color value.
Assuming the depth value is normalized (I can't remember off the top of my head whether this is the case, or if it's dependent on the pipeline or framebuffer setup) and if you just want greyscale, you could use
uint8_t map(float f) {
return (uint8_t)(f * 255.0f);
}
and inside your file writing loop you'd so something like
uint8_t grey = map(*row);
file.write(&grey, 1);
file.write(&grey, 1);
file.write(&grey, 1);
++row;
Alternatively if you want some sort of color gradient for easier visulization you'd want a more complex mapping function...
vec3 colorWheel(float normalizedHue) {
float v = normalizedHue * 6.f;
if (v < 0.f) {
return vec3(1.f, 0.f, 0.f);
} else if (v < 1.f) {
return vec3(1.f, v, 0.f);
} else if (v < 2.f) {
return vec3(1.f - (v-1.f), 1.f, 0.f);
} else if (v < 3.f) {
return vec3(0.f, 1.f, (v-2.f));
} else if (v < 4.f) {
return vec3(0.f, 1.f - (v-3.f), 1.f );
} else if (v < 5.f) {
return vec3((v-4.f), 0.f, 1.f );
} else if (v < 6.f) {
return vec3(1.f, 0.f, 1.f - (v-5.f));
} else {
return vec3(1.f, 0.f, 0.f);
}
}
and in your file output loop...
vec3 color = colorWheel(*row);
uint8_t r = map(color.r);
uint8_t g = map(color.g);
uint8_t b = map(color.b);
file.write(&r, 1);
file.write(&g, 1);
file.write(&b, 1);
++row;
I am creating text in a GDI+ GraphicsPath, using DrawString, and then outputting this to PDF as a path.
This is all working perfectly at the moment.
The time I have an issue is when the chosen font causes the outlines to overlap each other. I have an image example though being a new user i can't upload it... (seems pointless..?..)
I found a library and also someone who has achieved the same as what I am looking for in this blog
I have converted the code snippet to vb.net though I keep getting an empty solution from the library.
Has anybody else managed to pass in a graphicsPath containing a string and retrieve outlined text using this or a similar library?
Here's some C# code that works ...
using ClipperLib;
static public void PathToPolygon(GraphicsPath path, Polygons polys, Single scale)
{
GraphicsPathIterator pathIterator = new GraphicsPathIterator(path);
pathIterator.Rewind();
polys.Clear();
PointF[] points = new PointF[pathIterator.Count];
byte[] types = new byte[pathIterator.Count];
pathIterator.Enumerate(ref points, ref types);
int i = 0;
while (i < pathIterator.Count)
{
Polygon pg = new Polygon();
polys.Add(pg);
do {
IntPoint pt = new IntPoint((int)(points[i].X * scale), (int)(points[i].Y * scale));
pg.Add(pt);
i++;
}
while (i < pathIterator.Count && types[i] != 0);
}
}
static private PointF[] PolygonToPointFArray(Polygon pg, float scale)
{
PointF[] result = new PointF[pg.Count];
for (int i = 0; i < pg.Count; ++i)
{
result[i].X = (float)pg[i].X / scale;
result[i].Y = (float)pg[i].Y / scale;
}
return result;
}
private void DrawBitmap()
{
Font f = new Font("Arial", 90);
Pen myPen = new Pen(Color.FromArgb(196, 0xC3, 0xC9, 0xCF), (float)0.6);
SolidBrush myBrush = new SolidBrush(Color.FromArgb(127, 0xDD, 0xDD, 0xF0));
path.Reset();
Polygons polys;
path.AddString("ABC", f.FontFamily, (int)f.Style, f.Size, new Point(100, 100), null);
path.Flatten();
//scale all points up by 100 because Clipper uses integer coordinates
PathToPolygon(path, polys, 100);
path.Reset();
//offset polys remembering to multiply delta by scaling amount ...
polys = Clipper.OffsetPolygons(polys, 7 * 100, JoinType.jtRound);
for (int i = 0; i < polys.Count(); i++)
{
//reverses scaling ...
PointF[] pts2 = PolygonToPointFArray(polys[i], 100);
path.AddPolygon(pts2);
}
newgraphic.FillPath(myBrush, path);
newgraphic.DrawPath(myPen, path);
}
So I've been working in Processing for a few weeks now, and, though I'm not experienced in programming, I have moved on to more complex projects. I'm programming an evolution simulator, that spawns creatures with random properties.
Eventually, I'll add reproduction, but as of now the creatures just sort of float around the screen, and follow the mouse somewhat. It interacts with sound from the line in, but I commented those parts out so that it can be viewed on the canvas, it shouldn't really change the question, I just thought I would point it out.
As of now, the framerate is far less than ideal for me, and it slowly lowers as more creatures are spawned. Am I making some fundamental mistake, or am I just running too many functions per frame?
Here's the source code, and you can play with it in the browser here:
//import ddf.minim.*;
//import ddf.minim.signals.*;
//import ddf.minim.analysis.*;
//import ddf.minim.effects.*;
//Minim minim;
//AudioInput in;
boolean newCreature = true;
boolean matured[];
int ellipses[];
int hair[];
int maxCreatureNumber = 75;
//int volume;
//int volumeTolerance = 1;
int creatureIndex = -1;
int creatureX[];
int creatureY[];
float strokeWeightAttribute[];
float creatureSize[];
float creatureEndSize[];
float creatureXIncrement[];
float creatureYIncrement[];
float bubbleSize;
float easing = 0.05;
float angle = 0.00;
color colorAttribute[];
void setup() {
background(0);
size(1000,500);
noFill();
//minim = new Minim(this);
//in = minim.getLineIn(Minim.STEREO, 512);
creatureX = new int[maxCreatureNumber];
creatureY = new int[maxCreatureNumber];
ellipses = new int[maxCreatureNumber];
hair = new int[maxCreatureNumber];
strokeWeightAttribute = new float[maxCreatureNumber];
creatureEndSize = new float[maxCreatureNumber];
creatureSize = new float[maxCreatureNumber];
creatureXIncrement = new float[maxCreatureNumber];
creatureYIncrement = new float[maxCreatureNumber];
matured = new boolean[maxCreatureNumber];
colorAttribute = new color[maxCreatureNumber];
}
void draw() {
angle += 0.05;
fill(0, 50);
rect(-1, -1, 1001, 501);
// for(int i = 0; i < in.bufferSize() - 1; i++) {
// if(in.mix.get(i) * 50 > volumeTolerance) {
// volume++;
// }
// }
if(newCreature && creatureIndex < maxCreatureNumber - 1) {
initSpontaneousCreature();
}
updateCreatures();
// bubbleSize = volume/250;
bubbleSize += 0.01;
// volume = 0;
}
//void stop() {
// minim.stop();
// super.stop();
//}
void initSpontaneousCreature() {
creatureIndex++;
creatureEndSize[creatureIndex] = int(random(5, 20));
creatureX[creatureIndex] = int(random(1000));
if(creatureX[creatureIndex] >= 500) {
creatureX[creatureIndex] -= creatureEndSize[creatureIndex];
}
else {
creatureX[creatureIndex] += creatureEndSize[creatureIndex];
}
creatureY[creatureIndex] = int(random(500));
if(creatureY[creatureIndex] >= 250) {
creatureY[creatureIndex] -= creatureEndSize[creatureIndex];
}
else {
creatureY[creatureIndex] += creatureEndSize[creatureIndex];
}
ellipses[creatureIndex] = int(random(4));
hair[creatureIndex] = int(random(4));
strokeWeightAttribute[creatureIndex] = random(1, 4);
colorAttribute[creatureIndex] = color(int(random(20,255)), int(random(20,255)), int(random(20,255)));
matured[creatureIndex] = false;
newCreature = false;
while(ellipses[creatureIndex] == 0 && hair[creatureIndex] == 0) {
ellipses[creatureIndex] = int(random(4));
hair[creatureIndex] = int(random(4));
}
}
void updateCreatures() {
for(int n = 0; n <= creatureIndex; n++) {
if(matured[n]) {
creatureX[n] += ((((mouseX) - creatureX[n]) * easing) / (60/*-abs(volume/5))*/)) + random(-5, 6);
creatureY[n] += ((((mouseY) -creatureY[n]) * easing) / (60/*-abs(/*volume/5))*/)) + random(-5,6);
drawCreature();
}
else {
if(creatureEndSize[n] != creatureSize[n]) {
creatureSize[n] += bubbleSize;
if(creatureSize[n] > creatureEndSize[n]) {
creatureSize[n] -= (creatureSize[n] - creatureEndSize[n]);
}
}
else {
newCreature = true;
matured[n] = true;
// bubbleSize = 0;
}
drawCreature();
}
}
}
void drawCreature() {
for(int n = 0; n <= creatureIndex; n++) {
if(matured[n]) {
stroke(colorAttribute[n]);
strokeWeight(strokeWeightAttribute[n]);
for(int i = 0; i <= 4; i++) {
if(ellipses[n] == i) {
if(i == 0) {
}
else if (i == 1) {
pushMatrix();
translate(creatureX[n], creatureY[n]);
ellipse(creatureSize[n], creatureSize[n], creatureSize[n], creatureSize[n]);
rotate(radians(180));
ellipse(creatureSize[n], creatureSize[n], creatureSize[n], creatureSize[n]);
popMatrix();
}
else if(i == 2) {
pushMatrix();
translate(creatureX[n], creatureY[n]);
ellipse(creatureSize[n], creatureSize[n], creatureSize[n], creatureSize[n]);
rotate(radians(180));
ellipse(creatureSize[n], creatureSize[n], creatureSize[n], creatureSize[n]);
rotate(radians(270));
ellipse(creatureSize[n], creatureSize[n], creatureSize[n], creatureSize[n]);
popMatrix();
}
else if(i == 3) {
pushMatrix();
translate(creatureX[n], creatureY[n]);
ellipse(creatureSize[n], creatureSize[n], creatureSize[n], creatureSize[n]);
rotate(radians(90));
ellipse(creatureSize[n], creatureSize[n], creatureSize[n], creatureSize[n]);
rotate(radians(180));
ellipse(creatureSize[n], creatureSize[n], creatureSize[n], creatureSize[n]);
rotate(radians(270));
ellipse(creatureSize[n], creatureSize[n], creatureSize[n], creatureSize[n]);
popMatrix();
}
}
if(hair[n] == i) {
if(i == 0) {
}
else if (i == 1) {
pushMatrix();
translate(creatureX[n], creatureY[n]);
for(int j = 0; j <= 360; j+=70) {
rotate(j);
stroke(colorAttribute[n], random(255));
line(0,0, creatureSize[n] + random(10), creatureSize[n] + random(10));
}
popMatrix();
}
else if(i == 2) {
pushMatrix();
translate(creatureX[n], creatureY[n]);
for(int j = 0; j <= 360; j+=30) {
rotate(j);
stroke(colorAttribute[n], random(255));
line(0,0, creatureSize[n] + random(10), creatureSize[n] + random(10));
}
popMatrix();
}
else if(i == 3) {
pushMatrix();
translate(creatureX[n], creatureY[n]);
for(int j = 0; j <= 360; j+=1) {
rotate(j);
stroke(colorAttribute[n], random(255));
line(0,0, creatureSize[n] + random(10), creatureSize[n] + random(10));
}
popMatrix();
}
}
}
}
if(!matured[n]) {
stroke(abs(sin(angle) * 255));
//strokeWeight(5);
ellipse(creatureX[n], creatureY[n], creatureSize[n] * 5, creatureSize[n] * 5);
noStroke();
}
}
}
Right, as I suspected, all the unnecessary pushMatrix(), popMatrix() calls and the large amount of lines seemed to be the main culprits, still, there was a lot of redundant code.
I simply refactored the code in a cleaner manner and it seems to run fine.
Here is my 'improved' version:
int maxCreatures = 75;
int numCreatures = 0;
int spawnNthFrame = 50;//spawn a creature every 50 frames
Creature[] creatures;
void setup() {
background(0);
size(1000,500);
noFill();
creatures = new Creature[maxCreatures];
}
void draw() {
fill(0, 50);
rect(-1, -1, 1001, 501);
if(frameCount % spawnNthFrame == 0){
println("creatures: " + numCreatures);
if(numCreatures < maxCreatures) {
//Creature constructor float endSize,int x, int y,int ellipses,int hair,float strokeW,color c
creatures[numCreatures] = new Creature(random(5, 20),int(random(1000)),int(random(500)),int(random(4)),int(random(4)),random(1, 4),color(int(random(20,255)), int(random(20,255)), int(random(20,255))));
numCreatures++;
}
}
for(int i = 0; i < numCreatures; i++) creatures[i].update();
}
and the Creature class:
class Creature{
int x,y,cXInc,cYInc;//if x,y are ints, increments would be into, right?
float cStrokeWeight,cSize,cEndSize,cSizeInc = 0.01,easing = 0.05,angle = 0.00;
color cColor;
int hair,numHair,ellipses;
boolean matured = false;
Creature(float endSize,int x, int y,int ellipses,int hair,float strokeW,color c){
cEndSize = endSize;
this.x = x;
if(x >= 500) x -= cEndSize;
else x += cEndSize;
this.y = y;
if(y >= 250) x -= cEndSize;
else x += cEndSize;
this.ellipses = ellipses;
this.hair = hair;
if(hair == 1) numHair = 3;//~5, half that, draw through centre, etc.
if(hair == 2) numHair = 6;
if(hair == 3) numHair = 30;//no default value
cStrokeWeight = strokeW;
this.cColor = c;
}
void update(){
if(matured) {
x += (((mouseX - x) * easing) / 60) + random(-5, 6);
y += (((mouseY - y) * easing) / 60) + random(-5, 6);
}else {
if(cSize < cEndSize) cSize += cSizeInc;
else matured = true;
angle += 0.05;
}
this.draw();
}
void draw(){
if(matured){
stroke(cColor);
strokeWeight(cStrokeWeight);
if(ellipses == 1){//2 ellipses diagonally
ellipse(x,y,cSize,cSize);
ellipse(x+cSize,y+cSize,cSize,cSize);
}
if(ellipses == 2){
ellipse(x,y,cSize,cSize);
ellipse(x,y+cSize,cSize,cSize);
ellipse(x+cSize,y+cSize,cSize,cSize);
}
if(ellipses == 3){
ellipse(x,y,cSize,cSize);
ellipse(x+cSize,y,cSize,cSize);
ellipse(x,y+cSize,cSize,cSize);
ellipse(x+cSize,y+cSize,cSize,cSize);
}
float hairAngleInc = TWO_PI/numHair;//angle increment for each piece = 360/number of hair lines
float hairAngle,hairLength,hairCos,hairSin;
for(int i = 0; i < numHair; i++){
hairAngle = hairAngleInc * i;
hairCos = cos(hairAngle);
hairSin = sin(hairAngle);
hairLength = random(20);
stroke(cColor, random(255));
line(x + (hairCos * -hairLength),y + (hairSin * -hairLength), x + (hairCos * hairLength),y + (hairSin * hairLength));
}
}else{
stroke(abs(sin(angle) * 255));
ellipse(x,y, cSize * 5, cSize * 5);
}
}
}
Ok, now for the explanations.
First, I separated all the variables that were related to one creature from the 'global' ones that determine how the sketch runs (how many creatures get spawned, etc.).
This makes the main code about 25 lines long and altogether a bit below 100 lines which is less than half of the original.
The first part doesn't do anything special. In the draw() function, instead of creating a Creature every frame, I draw one every Nth frame using the spawnNthFrame variable, this made it easy to see which state of the creature made it slow. If you set a small number like 2 to that variable it should spawn a lot of creatures per frame.
The Creature class has all the properties the original code stored in arrays.
Instead of doing
pushMatrix();
translate();
ellipse();
rotate()
ellipse()
popMatrix();
I simply draw the ellipses at x,y.
A little hint on the rotations. I've noticed they were increments
of 90 degrees. Processing has some nice constants for 90,180,360 degrees
in radians: HALF_PI, PI, TWO_PI which can be handy sometimes.
Now for the 'hairy' situation, here's something I commented out for myself:
//if(i == 1) for(int j = 0; j <= 360; j+=70) , well 360/70 is about 5, if (i == 2) , 12 hair
//if = 3-> 360 lines ? do you really need that many lines, that thick ? how about 30 ? 5*12=60, but if you draw the lines through the centre, not from the centre, you can get away with half the lines
So there were 3 loops for drawing lines, each having different increments. Basically
there were either 360/70 lines, 360/30 lines and 360 lines.
Roughly about 5,12 and 360 lines. About the 5,12 lines, I kind of halved that by drawing 'diameter' lines across the centre as opposed to 'radius' lines from the centre.
Here's what I mean,
Also I think that 360 lines with that strokeWeight and the jittery motion will probably look like a bunch of lines hard to count, so I thought, why split hairs? :P
Maybe the creature will look pretty similar with about 60 radii which means 30 diameters.
Now to explain a bit of the trig functions used for this.
The main thing is the 'polar to cartesian' coordinates conversion:
Polar would be something like:
"I am moving on a circle to a direction described by an angle (much like one handle of a clock) and radius (distance from centre)."
and Cartesian
"I'm moving based on two axes (horizontal/X and vertical/Y), kind of like the streets of Manhattan, but I cheat and also move diagonally through walls."
If that makes any sense... :)
Anyway, you convert the angle and radius pair to the x and y pair using the formula:
x = cos(angle) * radius
y = sin(angle) * radius
For each line:
angle = hairAngle
radius = hairLength
So the line() with *x + (hairCos * -hairLength)* looks a bit like this:
x + (hairCos * -hairLength) =
move to x and from there move by hairLength
to the left(-) for the current angle (hairCos)
Similar for y, but using cos, so this puts the first point of the line in the opposite direct (-hairLength) of the angle moving from the centre (which is the Creature's x) and the second is 'diagonal'. Imagine drawing 'diagonals' (from (-x,-y) to (+x,+y)), but you also rotate these.
Update
Apparently copy/pasting this code works in javascript too (best viewed in Chromium/Chrome). You can also run it right here:
var maxCreatures = 75;
var numCreatures = 0;
var spawnNthFrame = 50;//spawn a creature every 50 frames
var creatures = [];
function setup() {
background(0);
createCanvas(1000,500);
noFill();
}
function draw() {
fill(0, 50);
rect(-1, -1, 1001, 501);
if(frameCount % spawnNthFrame === 0){
println("creatures: " + numCreatures);
if(numCreatures < maxCreatures) {
//Creature constructor float endSize,int x, int y,int ellipses,int hair,float strokeW,color c
creatures[numCreatures] = new Creature(random(5, 20),int(random(1000)),int(random(500)),int(random(4)),int(random(4)),random(1, 4),color(int(random(20,255)), int(random(20,255)), int(random(20,255))));
numCreatures++;
}
}
for(var i = 0; i < numCreatures; i++) creatures[i].update();
}
function Creature(endSize,x,y,ellipses,hair,strokeW,c){
this.x = x;
this.y = y;
this.ellipses = ellipses;
this.hair = hair;
this.numHair = 0;
this.cStrokeWeight = strokeW;
this.cColor = c;
this.cXInc = 0;
this.cYInc = 0.01;
this.cSize = 0;
this.cEndSize = endSize;
this.easing = 0.05;
this.angle = 0.0;
this.matured = false;
if(x >= 500) x -= this.cEndSize;
else x += this.cEndSize;
if(y >= 250) x -= this.cEndSize;
else x += this.cEndSize;
if(hair == 1) this.numHair = 3;//~5, half that, draw through centre, etc.
if(hair == 2) this.numHair = 6;
if(hair == 3) this.numHair = 30;//no default value
this.update = function(){
if(this.matured) {
this.x += (((mouseX - this.x) * this.easing) / 60) + random(-5, 6);
this.y += (((mouseY - this.y) * this.easing) / 60) + random(-5, 6);
}else {
if(this.cSize < this.cEndSize) this.cSize += this.cSizeInc;
else this.matured = true;
this.angle += 0.05;
}
this.draw();
}
this.draw = function(){
if(this.matured){
stroke(this.cColor);
strokeWeight(this.cStrokeWeight);
if(this.ellipses == 1){//2 ellipses diagonally
ellipse(this.x,this.y,this.cSize,this.cSize);
ellipse(this.x+this.cSize,this.y+this.cSize,this.cSize,this.cSize);
}
if(this.ellipses == 2){
ellipse(this.x,this.y,this.cSize,this.cSize);
ellipse(this.x,this.y+this.cSize,this.cSize,this.cSize);
ellipse(this.x+this.cSize,this.y+this.cSize,this.cSize,this.cSize);
}
if(this.ellipses == 3){
ellipse(this.x,this.y,this.cSize,this.cSize);
ellipse(this.x+this.cSize,this.y,this.cSize,this.cSize);
ellipse(this.x,this.y+this.cSize,this.cSize,this.cSize);
ellipse(this.x+this.cSize,this.y+this.cSize,this.cSize,this.cSize);
}
var hairAngleInc = TWO_PI/this.numHair;//angle increment for each piece = 360/number of hair lines
var hairAngle,hairLength,hairCos,hairSin;
for(var i = 0; i < this.numHair; i++){
hairAngle = hairAngleInc * i;
hairCos = cos(hairAngle);
hairSin = sin(hairAngle);
hairLength = random(20);
stroke(this.cColor, random(255));
line(this.x + (hairCos * -hairLength),this.y + (hairSin * -hairLength), this.x + (hairCos * hairLength),this.y + (hairSin * hairLength));
}
}else{
stroke(abs(sin(this.angle) * 255));
ellipse(this.x,this.y, this.cSize * 5, this.cSize * 5);
}
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.4.4/p5.min.js"></script>
You could use the frameRate(fps)function. What it does is, it specifies the number of frames to be displayed every second. However, If the processor is not fast enough to maintain the specified rate, it will not be achieved. For example, the function call frameRate(30) will attempt to refresh 30 times a second. It is recommended to set the frame rate within setup().
Remember, using draw() without specifying the frame rate, by default it will run at 60 fps.
Well, there's the good old random-pause method. It's the "poor man's profiler".
Just snapshot it a few times. That will show you exactly what's taking the most time. Those are the things you should see if you can make faster.
It will show up in increased framerate.