1 /* 2 * Copyright (c) 2011, 2017, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package com.sun.pisces; 27 28 /** 29 * PiscesRenderer class is basic public API accessing Pisces library capabilities. 30 * 31 * Pisces renderer is intended to draw directly into underlying data buffer of AbstractSurface. 32 * Basic implementation of AbstractSurface is e.g. GraphicsSurface. 33 * 34 * All coordinates are in 15.16 representation. ie. 13 will be passed as 13<<16. 35 * Simple use-case for PiscesRenderer together with GraphicsSurface would be e.g. - 36 *<br/> 37 * 38 * <code> 39 * <br/> 40 * GraphicsSurface surface = new GraphicsSurface();<br/> 41 * <br/> 42 * PiscesRenderer pr = new PiscesRenderer(surface);<br/> 43 * </code> 44 * <br/> 45 * <br/> 46 * Now, when we have instances ready, we can render something from our paint(Graphics g) method 47 * <br/><br/> 48 * <code><br/> 49 * void paint(Graphics g) {<br/> 50 * <dd> surface.bindTarget(g);<br/> 51 * //we set stroke color<br/> 52 * pr.setColor(0xFF, 0x00, 0xAF);<br/> 53 * // we set required Porter-Duff Compositing Rule<br/> 54 * pr.setComposite(RendererBase.COMPOSITE_SRC_OVER);<br/> 55 * <br/> 56 * //switch antialising on/off as required<br/> 57 * pr.setAntialiasing(true); // on<br/> 58 * <br/> 59 * pr.setTransform(ourTransform6Matrix);<br/> 60 * <br/> 61 * //and now let's draw something finally<br/> 62 * pr.beginRendering(RendererBase.WIND_EVEN_ODD);<br/> 63 * pr.moveTo(50 << 16, 100 << 16); // <br/> 64 * pr.lineTo(30<<16, 1<<16); 65 * pr.endRendering();<br/> 66 * <br/> 67 * surface.releaseTarget();<br/> 68 * </dd> 69 * }<br/> 70 * </code> 71 */ 72 public final class PiscesRenderer { 73 74 public static final int ARC_OPEN = 0; 75 public static final int ARC_CHORD = 1; 76 public static final int ARC_PIE = 2; 77 78 private long nativePtr = 0L; 79 private AbstractSurface surface; 80 81 /** 82 * Creates a renderer that will write into a given surface. 83 * 84 * @param surface destination surface 85 */ 86 public PiscesRenderer(AbstractSurface surface) { 87 this.surface = surface; 88 initialize(); 89 } 90 91 private native void initialize(); 92 93 /** 94 * Sets the current paint color. 95 * 96 * @param red a value between 0 and 255. 97 * @param green a value between 0 and 255. 98 * @param blue a value between 0 and 255. 99 * @param alpha a value between 0 and 255. 100 */ 101 public void setColor(int red, int green, int blue, int alpha) { 102 checkColorRange(red, "RED"); 103 checkColorRange(green, "GREEN"); 104 checkColorRange(blue, "BLUE"); 105 checkColorRange(alpha, "ALPHA"); 106 this.setColorImpl(red, green, blue, alpha); 107 } 108 109 private native void setColorImpl(int red, int green, int blue, int alpha); 110 111 private void checkColorRange(int v, String componentName) { 112 if (v < 0 || v > 255) { 113 throw new IllegalArgumentException(componentName + " color component is out of range"); 114 } 115 } 116 117 /** 118 * Sets the current paint color. An alpha value of 255 is used. Calling <code>setColor</code> also switches 119 * painting mode - i.e. if we have specified gradient, or texture previously, this will be overcome with <code>setColor</code> 120 * call. Also note, that 3-param <code>setColor</code> sets fully opaque RGB color. To draw with semi-transparent color 121 * use 4-param convenience method. 122 * 123 * @param red a value between 0 and 255. 124 * @param green a value between 0 and 255. 125 * @param blue a value between 0 and 255. 126 */ 127 public void setColor(int red, int green, int blue) { 128 setColor(red, green, blue, 255); 129 } 130 131 /** 132 * Sets current Compositing Rule (Porter-Duff) to be used in following rendering operation. Note that <code>compositeAlpha</code> 133 * is not changed. 134 * @param compositeRule one of <code>RendererBase.COMPOSITE_*</code> constants. 135 */ 136 public void setCompositeRule(int compositeRule) { 137 if (compositeRule != RendererBase.COMPOSITE_CLEAR && 138 compositeRule != RendererBase.COMPOSITE_SRC && 139 compositeRule != RendererBase.COMPOSITE_SRC_OVER) 140 { 141 throw new IllegalArgumentException("Invalid value for Composite-Rule"); 142 } 143 this.setCompositeRuleImpl(compositeRule); 144 } 145 146 private native void setCompositeRuleImpl(int compositeRule); 147 148 private native void setLinearGradientImpl(int x0, int y0, int x1, int y1, 149 int[] colors, 150 int cycleMethod, 151 Transform6 gradientTransform); 152 153 /** 154 * This method sets linear color-gradient data to be used as paint data in following rendering operation. 155 * Imagine, we want to draw simple gradient from blue to red color. Each pixel on line perpendicular to line L = [[x0,y0], [x1, y1]] will have same constant color. 156 * Pixels on perpendicular-line which passes [x0, y0] will be blue. Those on line passing [x1, y1] will be red. Colors on lines in between will be interpolated by <code>fractions</code>. 157 * @param x0 x-coordinate of the starting point of the linear gradient 158 * @param y0 y-coordinate of the starting point of the linear gradient 159 * @param x1 x-coordinate of the end point of the linear gradient 160 * @param y0 y-coordinate of the end point of the linear gradient 161 * @param fractions this array defines normalized distances in which color (rgba[i]) starts to fade into next color (rgba[i+1]). This distance from the point [x0,y0] is given as fraction[i]*l, where l is length of line [[x0,y0], [x1,y1]]. fraction[i+1] says, in what distance fraction[i+1]*l from [x0,y0] should color already have firm value of rgba[i+1]. Values passed in fractions should be from interval <0.0, 1.0>, in 15.16 format. 162 * @param rgba colors which the linear gradient passes through. Generally should be fulfilled this formula <code>rgba.length == fractions.length</code> 163 * @param cycleMethod some value from <code>GradientColorMap.CYCLE_*</code>. @see GradienColorMap 164 * @param gradientTransform transformation applied to gradient paint data. This way we can either transform gradient fill together with filled object or leave it as if transformed gradient-filled object was a window through which we observe gradient area. 165 * @see GradienColorMap 166 */ 167 public void setLinearGradient(int x0, int y0, int x1, int y1, 168 int[] fractions, int[] rgba, 169 int cycleMethod, 170 Transform6 gradientTransform) 171 { 172 final GradientColorMap gradientColorMap = new GradientColorMap(fractions, rgba, cycleMethod); 173 setLinearGradientImpl(x0, y0, x1, y1, 174 gradientColorMap.colors, cycleMethod, 175 gradientTransform == null ? new Transform6(1 << 16, 0, 0, 1 << 16, 0, 0) : gradientTransform); 176 } 177 178 /** 179 * This method sets linear color-gradient data to be used as paint data in following rendering operation. 180 * Imagine, we want to draw simple gradient from blue to red color. Each pixel on line perpendicular to line L = [[x0,y0], [x1, y1]] will have same constant color. 181 * Pixels on perpendicular-line which passes [x0, y0] will be blue. Those on line passing [x1, y1] will be red. Colors on lines in between will be interpolated by <code>fractions</code>. 182 * @param x0 x-coordinate of the starting point of the linear gradient 183 * @param y0 y-coordinate of the starting point of the linear gradient 184 * @param x1 x-coordinate of the end point of the linear gradient 185 * @param y0 y-coordinate of the end point of the linear gradient 186 * @param gradientColorMap The GradientColorMap calculated with @see calculateLinearGradient. 187 * @param gradientTransform transformation applied to gradient paint data. This way we can either transform gradient fill together with filled object or leave it as if transformed gradient-filled object was a window through which we observe gradient area. 188 * @see GradienColorMap 189 */ 190 public void setLinearGradient(int x0, int y0, int x1, int y1, 191 GradientColorMap gradientColorMap, 192 Transform6 gradientTransform) 193 { 194 setLinearGradientImpl(x0, y0, x1, y1, 195 gradientColorMap.colors, 196 gradientColorMap.cycleMethod, 197 gradientTransform == null ? new Transform6(1 << 16, 0, 0, 1 << 16, 0, 0) : gradientTransform); 198 } 199 200 /** 201 * Java2D-style linear gradient creation. The color changes proportionally 202 * between point P0 (color0) nad P1 (color1). Cycle method constants are 203 * defined in GradientColorMap (CYCLE_*). This is convenience method only. Same as if setLinearGradient method with 8 parameters was called with 204 * fractions = {0x0000, 0x10000}, rgba = {color0, color1} and identity transformation matrix. 205 * 206 * @param x0 x coordinate of point P0 207 * @param y0 y coordinate of point P0 208 * @param color0 color of P0 209 * @param x1 x coordinate of point P1 210 * @param y1 y coordinate of point P1 211 * @param color1 color of P1 212 * @param cycleMethod type of cycling of the gradient (NONE, REFLECT, REPEAT) 213 * 214 * As Pisces Gradient support was added to support features introduced in SVG, see e.g. http://www.w3.org/TR/SVG11/pservers.html for more information and examples. 215 */ 216 public void setLinearGradient(int x0, int y0, int color0, 217 int x1, int y1, int color1, 218 int cycleMethod) { 219 int[] fractions = {0x0000, 0x10000}; 220 int[] rgba = {color0, color1}; 221 Transform6 ident = new Transform6(1 << 16, 0, 0, 1 << 16, 0, 0); 222 setLinearGradient(x0, y0, x1, y1, fractions, rgba, cycleMethod, ident); 223 } 224 225 private native void setRadialGradientImpl(int cx, int cy, int fx, int fy, 226 int radius, 227 int[] colors, 228 int cycleMethod, 229 Transform6 gradientTransform); 230 231 /** 232 * This method sets radial gradient paint data to be used in subsequent rendering. Radial gradient data generated will be used to fill the touched pixels of the path we draw. 233 * 234 * @param cx cx, cy and radius triplet defines the largest circle for the gradient. 100% gradient stop is mapped to perimeter of this circle. 235 * @param cy 236 * @param fx fx,fy defines focal point of the gradient. ie. 0% gradient stop is mapped to fx,fy point. If cx == fx and cy == fy, then gradient consists of homocentric circles. If these relations are not met, gradient field is deformed and eccentric ovals can be observed. 237 * @param fy 238 * @param radius @see cx 239 * @param fractions @see setLinearGradient 240 * @param rgba @see setLinearGradient 241 * @param cycleMethod @see setLinearGradient 242 * @param gradientTransform @see setLinearGradient 243 * 244 * As Pisces Gradient support was added to support features introduced in SVG, see e.g. http://www.w3.org/TR/SVG11/pservers.html for more information and examples. 245 */ 246 247 public void setRadialGradient(int cx, int cy, int fx, int fy, 248 int radius, 249 int[] fractions, int[] rgba, 250 int cycleMethod, 251 Transform6 gradientTransform) 252 { 253 final GradientColorMap gradientColorMap = new GradientColorMap(fractions, rgba, cycleMethod); 254 setRadialGradientImpl(cx, cy, fx, fy, radius, 255 gradientColorMap.colors, cycleMethod, 256 gradientTransform == null ? new Transform6(1 << 16, 0, 0, 1 << 16, 0, 0) : gradientTransform); 257 } 258 259 /** 260 * This method sets radial gradient paint data to be used in subsequent rendering. Radial gradient data generated will be used to fill the touched pixels of the path we draw. 261 * 262 * @param cx cx, cy and radius triplet defines the largest circle for the gradient. 100% gradient stop is mapped to perimeter of this circle. 263 * @param cy 264 * @param fx fx,fy defines focal point of the gradient. ie. 0% gradient stop is mapped to fx,fy point. If cx == fx and cy == fy, then gradient consists of homocentric circles. If these relations are not met, gradient field is deformed and eccentric ovals can be observed. 265 * @param fy 266 * @param radius @see cx 267 * @param gradientColorMap @see setLinearGradient 268 * @param gradientTransform @see setLinearGradient 269 * 270 * As Pisces Gradient support was added to support features introduced in SVG, see e.g. http://www.w3.org/TR/SVG11/pservers.html for more information and examples. 271 */ 272 273 public void setRadialGradient(int cx, int cy, int fx, int fy, 274 int radius, 275 GradientColorMap gradientColorMap, 276 Transform6 gradientTransform) { 277 setRadialGradientImpl(cx, cy, fx, fy, radius, 278 gradientColorMap.colors, 279 gradientColorMap.cycleMethod, 280 gradientTransform == null ? new Transform6(1 << 16, 0, 0, 1 << 16, 0, 0) : gradientTransform); 281 } 282 283 public void setTexture(int imageType, int data[], int width, int height, int stride, 284 Transform6 textureTransform, boolean repeat, boolean hasAlpha) 285 { 286 this.inputImageCheck(width, height, 0, stride, data.length); 287 this.setTextureImpl(imageType, data, width, height, stride, textureTransform, repeat, hasAlpha); 288 } 289 290 private native void setTextureImpl(int imageType, int data[], int width, int height, int stride, 291 Transform6 textureTransform, boolean repeat, boolean hasAlpha); 292 293 /** 294 * Sets a clip rectangle for all primitives. Each primitive will be 295 * clipped to the intersection of this rectangle and the destination 296 * image bounds. 297 */ 298 public void setClip(int minX, int minY, int width, int height) { 299 final int x1 = Math.max(minX, 0); 300 final int y1 = Math.max(minY, 0); 301 final int x2 = Math.min(minX + width, surface.getWidth()); 302 final int y2 = Math.min(minY + height, surface.getHeight()); 303 this.setClipImpl(x1, y1, x2 - x1, y2 - y1); 304 } 305 306 private native void setClipImpl(int minX, int minY, int width, int height); 307 308 /** 309 * Resets the clip rectangle. Each primitive will be clipped only 310 * to the destination image bounds. 311 */ 312 public void resetClip() { 313 this.setClipImpl(0, 0, surface.getWidth(), surface.getHeight()); 314 } 315 316 /** 317 * Clears rectangle (x, y, x + w, y + h). Clear sets all pixels to transparent black (0x00000000 ARGB). 318 */ 319 public void clearRect(int x, int y, int w, int h) { 320 final int x1 = Math.max(x, 0); 321 final int y1 = Math.max(y, 0); 322 final int x2 = Math.min(x + w, surface.getWidth()); 323 final int y2 = Math.min(y + h, surface.getHeight()); 324 this.clearRectImpl(x1, y1, x2 - x1, y2 - y1); 325 } 326 327 private native void clearRectImpl(int x, int y, int w, int h); 328 329 public void fillRect(int x, int y, int w, int h) { 330 final int x1 = Math.max(x, 0); 331 final int y1 = Math.max(y, 0); 332 final int x2 = Math.min(x + w, surface.getWidth() << 16); 333 final int y2 = Math.min(y + h, surface.getHeight() << 16); 334 final int w2 = x2 - x1; 335 final int h2 = y2 - y1; 336 if (w2 > 0 && h2 > 0) { 337 this.fillRectImpl(x1, y1, w2, h2); 338 } 339 } 340 341 private native void fillRectImpl(int x, int y, int w, int h); 342 343 public void emitAndClearAlphaRow(byte[] alphaMap, int[] alphaDeltas, int pix_y, int pix_x_from, int pix_x_to, 344 int rowNum) 345 { 346 this.emitAndClearAlphaRow(alphaMap, alphaDeltas, pix_y, pix_x_from, pix_x_to, 0, rowNum); 347 } 348 349 public void emitAndClearAlphaRow(byte[] alphaMap, int[] alphaDeltas, int pix_y, int pix_x_from, int pix_x_to, 350 int pix_x_off, int rowNum) 351 { 352 if (pix_x_off < 0 || (pix_x_off + (pix_x_to - pix_x_from)) > alphaDeltas.length) { 353 throw new IllegalArgumentException("rendering range exceeds length of data"); 354 } 355 this.emitAndClearAlphaRowImpl(alphaMap, alphaDeltas, pix_y, pix_x_from, pix_x_to, pix_x_off, rowNum); 356 } 357 358 private native void emitAndClearAlphaRowImpl(byte[] alphaMap, int[] alphaDeltas, int pix_y, int pix_x_from, int pix_x_to, 359 int pix_x_off, int rowNum); 360 361 public void fillAlphaMask(byte[] mask, int x, int y, int width, int height, int offset, int stride) { 362 if (mask == null) { 363 throw new NullPointerException("Mask is NULL"); 364 } 365 this.inputImageCheck(width, height, offset, stride, mask.length); 366 this.fillAlphaMaskImpl(mask, x, y, width, height, offset, stride); 367 } 368 369 private native void fillAlphaMaskImpl(byte[] mask, int x, int y, int width, int height, int offset, int stride); 370 371 public void setLCDGammaCorrection(float gamma) { 372 if (gamma <= 0) { 373 throw new IllegalArgumentException("Gamma must be greater than zero"); 374 } 375 this.setLCDGammaCorrectionImpl(gamma); 376 } 377 378 private native void setLCDGammaCorrectionImpl(float gamma); 379 380 public void fillLCDAlphaMask(byte[] mask, int x, int y, int width, int height, int offset, int stride) 381 { 382 if (mask == null) { 383 throw new NullPointerException("Mask is NULL"); 384 } 385 this.inputImageCheck(width, height, offset, stride, mask.length); 386 this.fillLCDAlphaMaskImpl(mask, x, y, width, height, offset, stride); 387 } 388 389 private native void fillLCDAlphaMaskImpl(byte[] mask, int x, int y, int width, int height, int offset, int stride); 390 391 public void drawImage(int imageType, int imageMode, int data[], int width, int height, int offset, int stride, 392 Transform6 textureTransform, boolean repeat, 393 int bboxX, int bboxY, int bboxW, int bboxH, 394 int lEdge, int rEdge, int tEdge, int bEdge, 395 int txMin, int tyMin, int txMax, int tyMax, 396 boolean hasAlpha) 397 { 398 this.inputImageCheck(width, height, offset, stride, data.length); 399 this.drawImageImpl(imageType, imageMode, data, width, height, offset, stride, 400 textureTransform, repeat, 401 bboxX, bboxY, bboxW, bboxH, 402 lEdge, rEdge, tEdge, bEdge, 403 txMin, tyMin, txMax, tyMax, 404 hasAlpha); 405 } 406 407 private native void drawImageImpl(int imageType, int imageMode, int data[], int width, int height, int offset, int stride, 408 Transform6 textureTransform, boolean repeat, 409 int bboxX, int bboxY, int bboxW, int bboxH, 410 int lEdge, int rEdge, int tEdge, int bEdge, 411 int txMin, int tyMin, int txMax, int tyMax, 412 boolean hasAlpha); 413 414 private void inputImageCheck(int width, int height, int offset, int stride, int data_length) { 415 if (width < 0) { 416 throw new IllegalArgumentException("WIDTH must be positive"); 417 } 418 if (height < 0) { 419 throw new IllegalArgumentException("HEIGHT must be positive"); 420 } 421 if (offset < 0) { 422 throw new IllegalArgumentException("OFFSET must be positive"); 423 } 424 if (stride < 0) { 425 throw new IllegalArgumentException("STRIDE must be positive"); 426 } 427 if (stride < width) { 428 throw new IllegalArgumentException("STRIDE must be >= WIDTH"); 429 } 430 final int nbits = 32-Integer.numberOfLeadingZeros(stride) + 32-Integer.numberOfLeadingZeros(height); 431 if (nbits > 31) { 432 throw new IllegalArgumentException("STRIDE * HEIGHT is too large"); 433 } 434 if ((offset + stride*(height-1) + width) > data_length) { 435 throw new IllegalArgumentException("STRIDE * HEIGHT exceeds length of data"); 436 } 437 } 438 439 protected void finalize() { 440 this.nativeFinalize(); 441 } 442 443 /** 444 * Native finalizer. Releases native memory used by PiscesRenderer at lifetime. 445 */ 446 private native void nativeFinalize(); 447 }