Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 94 additions & 17 deletions core/src/processing/core/PShape.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,23 @@

package processing.core;

import java.awt.Image;
import java.awt.color.ColorSpace;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
import java.util.Base64.Decoder;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.nio.charset.StandardCharsets;
import javax.swing.ImageIcon;
import javax.xml.bind.DatatypeConverter;



import processing.core.PApplet;

Expand Down Expand Up @@ -106,6 +121,7 @@ public class PShape implements PConstants {

/** Texture or image data associated with this shape. */
protected PImage image;
protected String imagePath = null;

public static final String OUTSIDE_BEGIN_END_ERROR =
"%1$s can only be called between beginShape() and endShape()";
Expand Down Expand Up @@ -1645,6 +1661,10 @@ protected void drawPrimitive(PGraphics g) {
params[6], params[7]);

} else if (kind == RECT) {

if (imagePath != null){
loadImage(g);
}
if (image != null) {
int oldMode = g.imageMode;
g.imageMode(CORNER);
Expand Down Expand Up @@ -1879,6 +1899,63 @@ protected void drawPath(PGraphics g) {
g.endShape(close ? CLOSE : OPEN);
}

private void loadImage(PGraphics g){

if(this.imagePath.startsWith("data:image")){
loadBase64Image();
}

if(this.imagePath.startsWith("file://")){
loadFileSystemImage(g);
}
this.imagePath = null;
}

private void loadFileSystemImage(PGraphics g){
imagePath = imagePath.substring(7);
PImage loadedImage = g.parent.loadImage(imagePath);
if(loadedImage == null){
System.err.println("Error loading image file: " + imagePath);
}else{
setTexture(loadedImage);
}
}

private void loadBase64Image(){
String[] parts = this.imagePath.split(";base64,");
String extension = parts[0].substring(11);
String encodedData = parts[1];

byte[] decodedBytes = DatatypeConverter.parseBase64Binary(encodedData);

if(decodedBytes == null){
System.err.println("Decode Error on image: " + imagePath.substring(0, 20));
return;
}

Image awtImage = new ImageIcon(decodedBytes).getImage();

if (awtImage instanceof BufferedImage) {
BufferedImage buffImage = (BufferedImage) awtImage;
int space = buffImage.getColorModel().getColorSpace().getType();
if (space == ColorSpace.TYPE_CMYK) {
return;
}
}

PImage loadedImage = new PImage(awtImage);
if (loadedImage.width == -1) {
// error...
}

// if it's a .gif image, test to see if it has transparency
if (extension.equals("gif") || extension.equals("png") ||
extension.equals("unknown")) {
loadedImage.checkAlpha();
}

setTexture(loadedImage);
}

// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

Expand Down Expand Up @@ -2387,14 +2464,14 @@ public void setFill(boolean fill) {
/**
* ( begin auto-generated from PShape_setFill.xml )
*
* The <b>setFill()</b> method defines the fill color of a <b>PShape</b>.
* This method is used after shapes are created or when a shape is defined explicitly
* (e.g. <b>createShape(RECT, 20, 20, 80, 80)</b>) as shown in the above example.
* When a shape is created with <b>beginShape()</b> and <b>endShape()</b>, its
* attributes may be changed with <b>fill()</b> and <b>stroke()</b> within
* <b>beginShape()</b> and <b>endShape()</b>. However, after the shape is
* created, only the <b>setFill()</b> method can define a new fill value for
* the <b>PShape</b>.
* The <b>setFill()</b> method defines the fill color of a <b>PShape</b>.
* This method is used after shapes are created or when a shape is defined explicitly
* (e.g. <b>createShape(RECT, 20, 20, 80, 80)</b>) as shown in the above example.
* When a shape is created with <b>beginShape()</b> and <b>endShape()</b>, its
* attributes may be changed with <b>fill()</b> and <b>stroke()</b> within
* <b>beginShape()</b> and <b>endShape()</b>. However, after the shape is
* created, only the <b>setFill()</b> method can define a new fill value for
* the <b>PShape</b>.
*
* ( end auto-generated )
*
Expand Down Expand Up @@ -2543,14 +2620,14 @@ public void setStroke(boolean stroke) {
/**
* ( begin auto-generated from PShape_setStroke.xml )
*
* The <b>setStroke()</b> method defines the outline color of a <b>PShape</b>.
* This method is used after shapes are created or when a shape is defined
* explicitly (e.g. <b>createShape(RECT, 20, 20, 80, 80)</b>) as shown in
* the above example. When a shape is created with <b>beginShape()</b> and
* <b>endShape()</b>, its attributes may be changed with <b>fill()</b> and
* <b>stroke()</b> within <b>beginShape()</b> and <b>endShape()</b>.
* However, after the shape is created, only the <b>setStroke()</b> method
* can define a new stroke value for the <b>PShape</b>.
* The <b>setStroke()</b> method defines the outline color of a <b>PShape</b>.
* This method is used after shapes are created or when a shape is defined
* explicitly (e.g. <b>createShape(RECT, 20, 20, 80, 80)</b>) as shown in
* the above example. When a shape is created with <b>beginShape()</b> and
* <b>endShape()</b>, its attributes may be changed with <b>fill()</b> and
* <b>stroke()</b> within <b>beginShape()</b> and <b>endShape()</b>.
* However, after the shape is created, only the <b>setStroke()</b> method
* can define a new stroke value for the <b>PShape</b>.
*
* ( end auto-generated )
*
Expand Down Expand Up @@ -3442,4 +3519,4 @@ protected void colorCalcARGB(int argb, float alpha) {
calcAlpha = (calcAi != 255);
}

}
}
199 changes: 197 additions & 2 deletions core/src/processing/core/PShapeSVG.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@

package processing.core;

import static java.awt.Font.BOLD;
import static java.awt.Font.ITALIC;
import static java.awt.Font.PLAIN;
import processing.data.*;

// TODO replace these with PMatrix2D
Expand Down Expand Up @@ -329,6 +332,10 @@ protected PShape parseChild(XML elem) {
} else if (name.equals("rect")) {
shape = createShape(this, elem, true);
shape.parseRect();

} else if (name.equals("image")) {
shape = createShape(this, elem, true);
shape.parseImage();

} else if (name.equals("polygon")) {
shape = createShape(this, elem, true);
Expand Down Expand Up @@ -358,8 +365,10 @@ protected PShape parseChild(XML elem) {
// return new FontGlyph(this, elem);

} else if (name.equals("text")) { // || name.equals("font")) {
PGraphics.showWarning("Text and fonts in SVG files are " +
"not currently supported, convert text to outlines instead.");
return new Text(this, elem);

} else if (name.equals("tspan")) {
return new LineOfText(this, elem);

} else if (name.equals("filter")) {
PGraphics.showWarning("Filters are not supported.");
Expand Down Expand Up @@ -439,8 +448,23 @@ protected void parseRect() {
getFloatWithUnit(element, "height", svgHeight)
};
}


protected void parseImage() {
kind = RECT;
textureMode = NORMAL;

family = PRIMITIVE;
params = new float[] {
getFloatWithUnit(element, "x", svgWidth),
getFloatWithUnit(element, "y", svgHeight),
getFloatWithUnit(element, "width", svgWidth),
getFloatWithUnit(element, "height", svgHeight)
};

this.imagePath = element.getString("xlink:href");
}

/**
* Parse a polyline or polygon from an SVG file.
* Syntax defined at http://www.w3.org/TR/SVG/shapes.html#PointsBNF
Expand Down Expand Up @@ -1557,8 +1581,179 @@ public RadialGradient(PShapeSVG parent, XML properties) {


// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

public static float TEXT_QUALITY = 1;
protected PFont parseFont(XML properties) {

// FontFace fontFace = new FontFace(this, properties);
String fontFamily = null;
float size = 10;
int weight = PLAIN; // 0
int italic = 0;

if (properties.hasAttribute("style")) {
String styleText = properties.getString("style");
String[] styleTokens = PApplet.splitTokens(styleText, ";");

//PApplet.println(styleTokens);
for (int i = 0; i < styleTokens.length; i++) {

String[] tokens = PApplet.splitTokens(styleTokens[i], ":");
//PApplet.println(tokens);

tokens[0] = PApplet.trim(tokens[0]);

if (tokens[0].equals("font-style")) {
// PApplet.println("font-style: " + tokens[1]);
if (tokens[1].contains("italic")) {
italic = ITALIC;
}
} else if (tokens[0].equals("font-variant")) {
// PApplet.println("font-variant: " + tokens[1]);
// setFillOpacity(tokens[1]);

} else if (tokens[0].equals("font-weight")) {
// PApplet.println("font-weight: " + tokens[1]);

if (tokens[1].contains("bold")) {
weight = BOLD;
// PApplet.println("Bold weight ! ");
}


} else if (tokens[0].equals("font-stretch")) {
// not supported.

} else if (tokens[0].equals("font-size")) {
// PApplet.println("font-size: " + tokens[1]);
size = Float.parseFloat(tokens[1].split("px")[0]);
// PApplet.println("font-size-parsed: " + size);
} else if (tokens[0].equals("line-height")) {
// not supported

} else if (tokens[0].equals("font-family")) {
// PApplet.println("Font-family: " + tokens[1]);
fontFamily = tokens[1];

} else if (tokens[0].equals("text-align")) {
// not supported

} else if (tokens[0].equals("letter-spacing")) {
// not supported

} else if (tokens[0].equals("word-spacing")) {
// not supported

} else if (tokens[0].equals("writing-mode")) {
// not supported

} else if (tokens[0].equals("text-anchor")) {
// not supported

} else {
// Other attributes are not yet implemented
}
}
}
if (fontFamily == null) {
return null;
}
size = size * TEXT_QUALITY;

return createFont(fontFamily, weight | italic, size, true);
}

protected PFont createFont(String name, int weight, float size, boolean smooth) {

// System.out.println("Try to create a font of " + name + " family, " + weight);
java.awt.Font baseFont = new java.awt.Font(name, weight, (int) size); // PFont.findFont(name);ç

// System.out.println("Resulting family : " + baseFont.getFamily() + " " + baseFont.getStyle());
PFont outputPFont = new PFont(baseFont.deriveFont(size), smooth, null);

// System.out.println("Resulting PFont family : " + outputPFont.getName());
return outputPFont;
}

public static class Text extends PShapeSVG {

protected PFont font;

public Text(PShapeSVG parent, XML properties) {
super(parent, properties, true);

// get location
float x = Float.parseFloat(properties.getString("x"));
float y = Float.parseFloat(properties.getString("y"));

if (matrix == null) {
matrix = new PMatrix2D();
}
matrix.translate(x, y);

family = GROUP;

font = parseFont(properties);

}

// @Override
// public void drawImpl(PGraphics g){
// }
}

public static class LineOfText extends PShapeSVG {

String textToDisplay;
PFont font;

public LineOfText(PShapeSVG parent, XML properties) {

// TODO: child should ideally be parsed too...
// for inline content.
super(parent, properties, false);

// // get location
float x = Float.parseFloat(properties.getString("x"));
float y = Float.parseFloat(properties.getString("y"));

float parentX = Float.parseFloat(parent.element.getString("x"));
float parentY = Float.parseFloat(parent.element.getString("y"));

if (matrix == null) matrix = new PMatrix2D();
matrix.translate(x - parentX, (y - parentY) / 2f);

// get the first properties
parseColors(properties);
font = parseFont(properties);

// It is a line..
boolean isALine = properties.getString("role") == "line";
// NO inline content yet.
if (this.childCount > 0) {
}

String text = properties.getContent();
textToDisplay = text;
}

@Override
public void drawImpl(PGraphics g) {
if (font == null) {
font = ((Text) parent).font;
if (font == null) {
return;
}
}

pre(g);
g.textFont(font, font.size / TEXT_QUALITY);
g.text(textToDisplay, 0, 0);
post(g);
}

}

public static class Font extends PShapeSVG {
public FontFace face;

Expand Down