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 }