1 /* 2 * Copyright (c) 2009, 2014, 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.prism.impl.ps; 27 28 import java.security.AccessController; 29 import java.security.PrivilegedAction; 30 import com.sun.javafx.font.FontResource; 31 import com.sun.javafx.font.FontStrike; 32 import com.sun.javafx.font.Metrics; 33 import com.sun.javafx.font.PrismFontFactory; 34 import com.sun.javafx.geom.BaseBounds; 35 import com.sun.javafx.geom.Point2D; 36 import com.sun.javafx.geom.RectBounds; 37 import com.sun.javafx.geom.Rectangle; 38 import com.sun.javafx.geom.Shape; 39 import com.sun.javafx.geom.transform.Affine2D; 40 import com.sun.javafx.geom.transform.Affine3D; 41 import com.sun.javafx.geom.transform.AffineBase; 42 import com.sun.javafx.geom.transform.BaseTransform; 43 import com.sun.javafx.geom.transform.NoninvertibleTransformException; 44 import com.sun.javafx.scene.text.GlyphList; 45 import com.sun.javafx.sg.prism.NGLightBase; 46 import com.sun.prism.BasicStroke; 47 import com.sun.prism.CompositeMode; 48 import com.sun.prism.MaskTextureGraphics; 49 import com.sun.prism.MultiTexture; 50 import com.sun.prism.PixelFormat; 51 import com.sun.prism.RTTexture; 52 import com.sun.prism.ReadbackGraphics; 53 import com.sun.prism.ReadbackRenderTarget; 54 import com.sun.prism.RenderTarget; 55 import com.sun.prism.Texture; 56 import com.sun.prism.impl.BaseGraphics; 57 import com.sun.prism.impl.GlyphCache; 58 import com.sun.prism.impl.PrismSettings; 59 import com.sun.prism.impl.VertexBuffer; 60 import com.sun.prism.impl.ps.BaseShaderContext.MaskType; 61 import com.sun.prism.impl.shape.MaskData; 62 import com.sun.prism.impl.shape.ShapeUtil; 63 import com.sun.prism.paint.Color; 64 import com.sun.prism.paint.Gradient; 65 import com.sun.prism.paint.ImagePattern; 66 import com.sun.prism.paint.LinearGradient; 67 import com.sun.prism.paint.Paint; 68 import com.sun.prism.paint.RadialGradient; 69 import com.sun.prism.ps.Shader; 70 import com.sun.prism.ps.ShaderGraphics; 71 72 public abstract class BaseShaderGraphics 73 extends BaseGraphics 74 implements ShaderGraphics, ReadbackGraphics, MaskTextureGraphics 75 { 76 private static Affine2D TEMP_TX2D = new Affine2D(); 77 private static Affine3D TEMP_TX3D = new Affine3D(); 78 79 private final BaseShaderContext context; 80 private Shader externalShader; 81 private boolean isComplexPaint; 82 83 protected BaseShaderGraphics(BaseShaderContext context, 84 RenderTarget renderTarget) 85 { 86 super(context, renderTarget); 87 this.context = context; 88 } 89 90 BaseShaderContext getContext() { 91 return context; 92 } 93 94 boolean isComplexPaint() { 95 return isComplexPaint; 96 } 97 98 public void getPaintShaderTransform(Affine3D ret) { 99 ret.setTransform(getTransformNoClone()); 100 } 101 102 public Shader getExternalShader() { 103 return externalShader; 104 } 105 106 public void setExternalShader(Shader shader) { 107 this.externalShader = shader; 108 context.setExternalShader(this, shader); 109 } 110 111 @Override 112 public void setPaint(Paint paint) { 113 if (paint.getType().isGradient()) { 114 Gradient grad = (Gradient)paint; 115 isComplexPaint = grad.getNumStops() > PaintHelper.MULTI_MAX_FRACTIONS; 116 } else { 117 isComplexPaint = false; 118 } 119 super.setPaint(paint); 120 } 121 122 private NGLightBase lights[] = null; 123 124 public void setLights(NGLightBase lights[]) { this.lights = lights; } 125 126 public final NGLightBase[] getLights() { return this.lights; } 127 128 @Override 129 public void drawTexture(Texture tex, 130 float dx1, float dy1, float dx2, float dy2, 131 float sx1, float sy1, float sx2, float sy2) 132 { 133 // intercept MultiTexture operations 134 // FIXME: this should be pushed up to Graphics interface so that non-shader 135 // renderers can use MultiTexture too 136 if (tex instanceof MultiTexture) { 137 drawMultiTexture((MultiTexture)tex, dx1, dy1, dx2, dy2, sx1, sy1, sx2, sy2); 138 } else { 139 super.drawTexture(tex, dx1, dy1, dx2, dy2, sx1, sy1, sx2, sy2); 140 } 141 } 142 143 @Override 144 public void drawTexture3SliceH(Texture tex, 145 float dx1, float dy1, float dx2, float dy2, 146 float sx1, float sy1, float sx2, float sy2, 147 float dh1, float dh2, float sh1, float sh2) 148 { 149 if (!(tex instanceof MultiTexture)) { 150 super.drawTexture3SliceH(tex, 151 dx1, dy1, dx2, dy2, 152 sx1, sy1, sx2, sy2, 153 dh1, dh2, sh1, sh2); 154 return; 155 } 156 MultiTexture mtex = (MultiTexture) tex; 157 drawMultiTexture(mtex, dx1, dy1, dh1, dy2, sx1, sy1, sh1, sy2); 158 drawMultiTexture(mtex, dh1, dy1, dh2, dy2, sh1, sy1, sh2, sy2); 159 drawMultiTexture(mtex, dh2, dy1, dx2, dy2, sh2, sy1, sx2, sy2); 160 } 161 162 @Override 163 public void drawTexture3SliceV(Texture tex, 164 float dx1, float dy1, float dx2, float dy2, 165 float sx1, float sy1, float sx2, float sy2, 166 float dv1, float dv2, float sv1, float sv2) 167 { 168 if (!(tex instanceof MultiTexture)) { 169 super.drawTexture3SliceV(tex, 170 dx1, dy1, dx2, dy2, 171 sx1, sy1, sx2, sy2, 172 dv1, dv2, sv1, sv2); 173 return; 174 } 175 MultiTexture mtex = (MultiTexture) tex; 176 drawMultiTexture(mtex, dx1, dy1, dx2, dv1, sx1, sy1, sx2, sv1); 177 drawMultiTexture(mtex, dx1, dv1, dx2, dv2, sx1, sv1, sx2, sv2); 178 drawMultiTexture(mtex, dx1, dv2, dx2, dy2, sx1, sv2, sx2, sy2); 179 } 180 181 @Override 182 public void drawTexture9Slice(Texture tex, 183 float dx1, float dy1, float dx2, float dy2, 184 float sx1, float sy1, float sx2, float sy2, 185 float dh1, float dv1, float dh2, float dv2, 186 float sh1, float sv1, float sh2, float sv2) 187 { 188 if (!(tex instanceof MultiTexture)) { 189 super.drawTexture9Slice(tex, 190 dx1, dy1, dx2, dy2, 191 sx1, sy1, sx2, sy2, 192 dh1, dv1, dh2, dv2, 193 sh1, sv1, sh2, sv2); 194 return; 195 } 196 MultiTexture mtex = (MultiTexture) tex; 197 drawMultiTexture(mtex, dx1, dy1, dh1, dv1, sx1, sy1, sh1, sv1); 198 drawMultiTexture(mtex, dh1, dy1, dh2, dv1, sh1, sy1, sh2, sv1); 199 drawMultiTexture(mtex, dh2, dy1, dx2, dv1, sh2, sy1, sx2, sv1); 200 201 drawMultiTexture(mtex, dx1, dv1, dh1, dv2, sx1, sv1, sh1, sv2); 202 drawMultiTexture(mtex, dh1, dv1, dh2, dv2, sh1, sv1, sh2, sv2); 203 drawMultiTexture(mtex, dh2, dv1, dx2, dv2, sh2, sv1, sx2, sv2); 204 205 drawMultiTexture(mtex, dx1, dv2, dh1, dy2, sx1, sv2, sh1, sy2); 206 drawMultiTexture(mtex, dh1, dv2, dh2, dy2, sh1, sv2, sh2, sy2); 207 drawMultiTexture(mtex, dh2, dv2, dx2, dy2, sh2, sv2, sx2, sy2); 208 } 209 210 private static float calculateScaleFactor(float contentDim, float physicalDim) { 211 if (contentDim == physicalDim) { 212 return 1f; 213 } 214 // we have to subtract 1 to eliminate the "green line of death" 215 return (contentDim-1) / physicalDim; 216 } 217 218 protected void drawMultiTexture(MultiTexture tex, 219 float dx1, float dy1, float dx2, float dy2, 220 float sx1, float sy1, float sx2, float sy2) 221 { 222 // The following is safe; this method does not mutate the transform 223 BaseTransform xform = getTransformNoClone(); 224 if (isSimpleTranslate) { 225 xform = IDENT; 226 dx1 += transX; 227 dy1 += transY; 228 dx2 += transX; 229 dy2 += transY; 230 } 231 232 Texture textures[] = ((MultiTexture)tex).getTextures(); 233 Shader shader = context.validateTextureOp(this, xform, textures, tex.getPixelFormat()); 234 235 if (null == shader) { 236 // FIXME: throw exception?? We can't do anything without the shader.. 237 return; 238 } 239 240 if (tex.getPixelFormat() == PixelFormat.MULTI_YCbCr_420) { 241 Texture lumaTex = textures[PixelFormat.YCBCR_PLANE_LUMA]; 242 Texture cbTex = textures[PixelFormat.YCBCR_PLANE_CHROMABLUE]; 243 Texture crTex = textures[PixelFormat.YCBCR_PLANE_CHROMARED]; 244 245 // sampler scaling factors 246 float imgWidth = (float)tex.getContentWidth(); 247 float imgHeight = (float)tex.getContentHeight(); 248 float lumaScaleX, lumaScaleY; 249 float alphaScaleX, alphaScaleY; 250 float cbScaleX, cbScaleY; 251 float crScaleX, crScaleY; 252 253 lumaScaleX = calculateScaleFactor(imgWidth, (float)lumaTex.getPhysicalWidth()); 254 lumaScaleY = calculateScaleFactor(imgHeight, (float)lumaTex.getPhysicalHeight()); 255 256 if (textures.length > 3) { 257 Texture alphaTex = textures[PixelFormat.YCBCR_PLANE_ALPHA]; 258 alphaScaleX = calculateScaleFactor(imgWidth, (float)alphaTex.getPhysicalWidth()); 259 alphaScaleY = calculateScaleFactor(imgHeight, (float)alphaTex.getPhysicalHeight()); 260 } else { 261 alphaScaleX = alphaScaleY = 0f; 262 } 263 264 float chromaWidth = (float)Math.floor((double)imgWidth/2.0); 265 float chromaHeight = (float)Math.floor((double)imgHeight/2.0); 266 267 cbScaleX = calculateScaleFactor(chromaWidth, (float)cbTex.getPhysicalWidth()); 268 cbScaleY = calculateScaleFactor(chromaHeight, (float)cbTex.getPhysicalHeight()); 269 crScaleX = calculateScaleFactor(chromaWidth, (float)crTex.getPhysicalWidth()); 270 crScaleY = calculateScaleFactor(chromaHeight, (float)crTex.getPhysicalHeight()); 271 272 shader.setConstant("lumaAlphaScale", lumaScaleX, lumaScaleY, alphaScaleX, alphaScaleY); 273 shader.setConstant("cbCrScale", cbScaleX, cbScaleY, crScaleX, crScaleY); 274 275 float tx1 = sx1 / imgWidth; 276 float ty1 = sy1 / imgHeight; 277 float tx2 = sx2 / imgWidth; 278 float ty2 = sy2 / imgHeight; 279 280 VertexBuffer vb = context.getVertexBuffer(); 281 vb.addQuad(dx1, dy1, dx2, dy2, tx1, ty1, tx2, ty2); 282 } else { 283 // should have been caught by validateTextureOp, but just in case 284 throw new UnsupportedOperationException("Unsupported multitexture format "+tex.getPixelFormat()); 285 } 286 } 287 288 public void drawTextureRaw2(Texture src1, Texture src2, 289 float dx1, float dy1, float dx2, float dy2, 290 float t1x1, float t1y1, float t1x2, float t1y2, 291 float t2x1, float t2y1, float t2x2, float t2y2) 292 { 293 // The following is safe; this method does not mutate the transform 294 BaseTransform xform = getTransformNoClone(); 295 if (isSimpleTranslate) { 296 xform = IDENT; 297 dx1 += transX; 298 dy1 += transY; 299 dx2 += transX; 300 dy2 += transY; 301 } 302 context.validateTextureOp(this, xform, src1, src2, 303 PixelFormat.INT_ARGB_PRE); 304 305 VertexBuffer vb = context.getVertexBuffer(); 306 vb.addQuad(dx1, dy1, dx2, dy2, 307 t1x1, t1y1, t1x2, t1y2, 308 t2x1, t2y1, t2x2, t2y2); 309 } 310 311 public void drawMappedTextureRaw2(Texture src1, Texture src2, 312 float dx1, float dy1, float dx2, float dy2, 313 float t1x11, float t1y11, float t1x21, float t1y21, 314 float t1x12, float t1y12, float t1x22, float t1y22, 315 float t2x11, float t2y11, float t2x21, float t2y21, 316 float t2x12, float t2y12, float t2x22, float t2y22) 317 { 318 // The following is safe; this method does not mutate the transform 319 BaseTransform xform = getTransformNoClone(); 320 if (isSimpleTranslate) { 321 xform = IDENT; 322 dx1 += transX; 323 dy1 += transY; 324 dx2 += transX; 325 dy2 += transY; 326 } 327 context.validateTextureOp(this, xform, src1, src2, 328 PixelFormat.INT_ARGB_PRE); 329 330 VertexBuffer vb = context.getVertexBuffer(); 331 vb.addMappedQuad(dx1, dy1, dx2, dy2, 332 t1x11, t1y11, t1x21, t1y21, 333 t1x12, t1y12, t1x22, t1y22, 334 t2x11, t2y11, t2x21, t2y21, 335 t2x12, t2y12, t2x22, t2y22); 336 } 337 338 public void drawPixelsMasked(RTTexture imgtex, RTTexture masktex, 339 int dx, int dy, int dw, int dh, 340 int ix, int iy, int mx, int my) 341 { 342 if (dw <= 0 || dh <= 0) return; 343 float iw = imgtex.getPhysicalWidth(); 344 float ih = imgtex.getPhysicalHeight(); 345 float mw = masktex.getPhysicalWidth(); 346 float mh = masktex.getPhysicalHeight(); 347 float dx1 = dx; 348 float dy1 = dy; 349 float dx2 = dx + dw; 350 float dy2 = dy + dh; 351 float ix1 = ix / iw; 352 float iy1 = iy / ih; 353 float ix2 = (ix + dw) / iw; 354 float iy2 = (iy + dh) / ih; 355 float mx1 = mx / mw; 356 float my1 = my / mh; 357 float mx2 = (mx + dw) / mw; 358 float my2 = (my + dh) / mh; 359 context.validateMaskTextureOp(this, IDENT, imgtex, masktex, 360 PixelFormat.INT_ARGB_PRE); 361 VertexBuffer vb = context.getVertexBuffer(); 362 vb.addQuad(dx1, dy1, dx2, dy2, 363 ix1, iy1, ix2, iy2, 364 mx1, my1, mx2, my2); 365 } 366 367 public void maskInterpolatePixels(RTTexture imgtex, RTTexture masktex, 368 int dx, int dy, int dw, int dh, 369 int ix, int iy, int mx, int my) 370 { 371 if (dw <= 0 || dh <= 0) return; 372 float iw = imgtex.getPhysicalWidth(); 373 float ih = imgtex.getPhysicalHeight(); 374 float mw = masktex.getPhysicalWidth(); 375 float mh = masktex.getPhysicalHeight(); 376 float dx1 = dx; 377 float dy1 = dy; 378 float dx2 = dx + dw; 379 float dy2 = dy + dh; 380 float ix1 = ix / iw; 381 float iy1 = iy / ih; 382 float ix2 = (ix + dw) / iw; 383 float iy2 = (iy + dh) / ih; 384 float mx1 = mx / mw; 385 float my1 = my / mh; 386 float mx2 = (mx + dw) / mw; 387 float my2 = (my + dh) / mh; 388 CompositeMode oldmode = getCompositeMode(); 389 setCompositeMode(CompositeMode.DST_OUT); 390 context.validateTextureOp(this, IDENT, masktex, 391 PixelFormat.INT_ARGB_PRE); 392 VertexBuffer vb = context.getVertexBuffer(); 393 vb.addQuad(dx1, dy1, dx2, dy2, 394 mx1, my1, mx2, my2); 395 396 setCompositeMode(CompositeMode.ADD); 397 context.validateMaskTextureOp(this, IDENT, imgtex, masktex, 398 PixelFormat.INT_ARGB_PRE); 399 vb.addQuad(dx1, dy1, dx2, dy2, 400 ix1, iy1, ix2, iy2, 401 mx1, my1, mx2, my2); 402 403 setCompositeMode(oldmode); 404 } 405 406 private void renderWithComplexPaint(Shape shape, BasicStroke stroke, 407 float bx, float by, float bw, float bh) 408 { 409 // creating/updating the mask texture may unset the current 410 // texture used by pending vertices, so flush the vertex buffer first 411 context.flushVertexBuffer(); 412 413 // The following is safe; this method does not mutate the transform 414 BaseTransform xform = getTransformNoClone(); 415 MaskData maskData = 416 ShapeUtil.rasterizeShape(shape, stroke, getFinalClipNoClone(), xform, true); 417 int maskW = maskData.getWidth(); 418 int maskH = maskData.getHeight(); 419 420 float dx1 = maskData.getOriginX(); 421 float dy1 = maskData.getOriginY(); 422 float dx2 = dx1 + maskW; 423 float dy2 = dy1 + maskH; 424 425 // Note that we could use multitexturing here (upload mask 426 // data to texture unit 0 and paint data to texture unit 1 427 // then set the texture mode to modulate) but this operation is 428 // already plenty slow and not worth optimizing at this time, 429 // so for now we will merge the mask with the paint data in software. 430 Gradient grad = (Gradient)paint; 431 TEMP_TX2D.setToTranslation(-dx1, -dy1); 432 TEMP_TX2D.concatenate(xform); 433 Texture tex = context.getGradientTexture(grad, TEMP_TX2D, 434 maskW, maskH, maskData, 435 bx, by, bw, bh); 436 437 float tx1 = 0f; 438 float ty1 = 0f; 439 float tx2 = tx1 + ((float)maskW) / tex.getPhysicalWidth(); 440 float ty2 = ty1 + ((float)maskH) / tex.getPhysicalHeight(); 441 442 // the mask has been generated in device space, so we use 443 // identity transform here 444 VertexBuffer vb = context.getVertexBuffer(); 445 context.validateTextureOp(this, IDENT, tex, null, tex.getPixelFormat()); 446 vb.addQuad(dx1, dy1, dx2, dy2, tx1, ty1, tx2, ty2); 447 tex.unlock(); 448 } 449 450 @Override 451 protected void renderShape(Shape shape, BasicStroke stroke, 452 float bx, float by, float bw, float bh) 453 { 454 if (isComplexPaint) { 455 renderWithComplexPaint(shape, stroke, bx, by, bw, bh); 456 return; 457 } 458 459 // creating/updating the mask texture may unset the current 460 // texture used by pending vertices, so flush the vertex buffer first 461 context.flushVertexBuffer(); 462 463 // The following is safe; this method does not mutate the transform 464 BaseTransform xform = getTransformNoClone(); 465 MaskData maskData = 466 ShapeUtil.rasterizeShape(shape, stroke, getFinalClipNoClone(), xform, true); 467 Texture maskTex = context.getMaskTexture(maskData, false); 468 int maskW = maskData.getWidth(); 469 int maskH = maskData.getHeight(); 470 471 float dx1 = maskData.getOriginX(); 472 float dy1 = maskData.getOriginY(); 473 float dx2 = dx1 + maskW; 474 float dy2 = dy1 + maskH; 475 float tx1 = 0f; 476 float ty1 = 0f; 477 float tx2 = tx1 + ((float)maskW) / maskTex.getPhysicalWidth(); 478 float ty2 = ty1 + ((float)maskH) / maskTex.getPhysicalHeight(); 479 480 if (PrismSettings.primTextureSize != 0) { 481 // the mask has been generated in device space, so we use 482 // identity transform here 483 Shader shader = 484 context.validatePaintOp(this, IDENT, MaskType.ALPHA_TEXTURE, maskTex, 485 bx, by, bw, bh); 486 487 VertexBuffer vb = context.getVertexBuffer(); 488 vb.addQuad(dx1, dy1, dx2, dy2, tx1, ty1, tx2, ty2, 489 getPaintTextureTx(xform, shader, bx, by, bw, bh)); 490 } else { 491 // the mask has been generated in device space, so we use 492 // identity transform here 493 context.validatePaintOp(this, IDENT, maskTex, bx, by, bw, bh); 494 495 VertexBuffer vb = context.getVertexBuffer(); 496 vb.addQuad(dx1, dy1, dx2, dy2, tx1, ty1, tx2, ty2); 497 } 498 maskTex.unlock(); 499 } 500 501 private static float getStrokeExpansionFactor(BasicStroke stroke) { 502 if (stroke.getType() == BasicStroke.TYPE_OUTER) { 503 return 1f; 504 } else if (stroke.getType() == BasicStroke.TYPE_CENTERED) { 505 return 0.5f; 506 } else { 507 return 0f; 508 } 509 } 510 511 private static final float FRINGE_FACTOR; 512 static { 513 String v = (String) AccessController.doPrivileged((PrivilegedAction) () -> System.getProperty("prism.primshaderpad")); 514 if (v == null) { 515 FRINGE_FACTOR = -0.5f; 516 } else { 517 FRINGE_FACTOR = -Float.valueOf(v); 518 System.out.println("Prism ShaderGraphics primitive shader pad = "+FRINGE_FACTOR); 519 } 520 } 521 522 private BaseTransform extract3Dremainder(BaseTransform xform) { 523 if (xform.is2D()) { 524 return IDENT; 525 } 526 TEMP_TX3D.setTransform(xform); 527 TEMP_TX2D.setTransform(xform.getMxx(), xform.getMyx(), 528 xform.getMxy(), xform.getMyy(), 529 xform.getMxt(), xform.getMyt()); 530 try { 531 TEMP_TX2D.invert(); 532 TEMP_TX3D.concatenate(TEMP_TX2D); 533 } catch (NoninvertibleTransformException ex) { 534 } 535 return TEMP_TX3D; 536 } 537 538 private void renderGeneralRoundedRect(float rx, float ry, float rw, float rh, 539 float arcw, float arch, 540 MaskType type, BasicStroke stroke) 541 { 542 // NOTE: using floats here for now, not sure if it's a problem yet... 543 float bx, by, bw, bh, ifractw, ifracth; 544 float ox, oy, wdx, wdy, hdx, hdy; 545 if (stroke == null) { 546 bx = rx; 547 by = ry; 548 bw = rw; 549 bh = rh; 550 ifractw = ifracth = 0f; 551 } else { 552 float sw = stroke.getLineWidth(); 553 float ow = getStrokeExpansionFactor(stroke) * sw; 554 bx = rx - ow; 555 by = ry - ow; 556 ow *= 2f; 557 bw = rw + ow; 558 bh = rh + ow; 559 if (arcw > 0 && arch > 0) { 560 arcw += ow; 561 arch += ow; 562 } else { 563 if (stroke.getLineJoin() == BasicStroke.JOIN_ROUND) { 564 arcw = arch = ow; 565 type = MaskType.DRAW_ROUNDRECT; 566 } else { 567 arcw = arch = 0f; 568 } 569 } 570 ifractw = (bw - sw * 2f) / bw; 571 ifracth = (bh - sw * 2f) / bh; 572 if (ifractw <= 0f || ifracth <= 0f) { 573 type = type.getFillType(); 574 } 575 } 576 577 // The following is safe; this method does not mutate the transform 578 BaseTransform xform = getTransformNoClone(); 579 BaseTransform rendertx; 580 if (isSimpleTranslate) { 581 wdx = hdy = 1f; 582 wdy = hdx = 0f; 583 ox = bx + transX; 584 oy = by + transY; 585 rendertx = IDENT; 586 } else { 587 rendertx = extract3Dremainder(xform); 588 wdx = (float)xform.getMxx(); 589 hdx = (float)xform.getMxy(); 590 wdy = (float)xform.getMyx(); 591 hdy = (float)xform.getMyy(); 592 ox = (bx * wdx) + (by * hdx) + (float)xform.getMxt(); 593 oy = (bx * wdy) + (by * hdy) + (float)xform.getMyt(); 594 } 595 596 wdx *= bw; 597 wdy *= bw; 598 hdx *= bh; 599 hdy *= bh; 600 601 float arcfractw = arcw / bw; 602 float arcfracth = arch / bh; 603 renderGeneralRoundedPgram(ox, oy, wdx, wdy, hdx, hdy, 604 arcfractw, arcfracth, ifractw, ifracth, 605 rendertx, type, rx, ry, rw, rh); 606 } 607 608 private void renderGeneralRoundedPgram(float ox, float oy, 609 float wvecx, float wvecy, 610 float hvecx, float hvecy, 611 float arcfractw, float arcfracth, 612 float ifractw, float ifracth, 613 BaseTransform rendertx, MaskType type, 614 float rx, float ry, float rw, float rh) 615 { 616 float wlen = len(wvecx, wvecy); 617 float hlen = len(hvecx, hvecy); 618 if (wlen == 0 || hlen == 0) { 619 // parallelogram has collapsed to a line or point 620 return; 621 } 622 623 // Calculate the 4 corners of the pgram in device space. 624 // Note that the UL,UR,LL,LR (Upper/Lower Left/Right) designations 625 // are virtual since the wdxy and hdxy vectors can point in any 626 // direction depending on the transform being applied. 627 float xUL = ox; 628 float yUL = oy; 629 float xUR = ox + wvecx; 630 float yUR = oy + wvecy; 631 float xLL = ox + hvecx; 632 float yLL = oy + hvecy; 633 float xLR = xUR + hvecx; 634 float yLR = yUR + hvecy; 635 636 // Calculate the unit vectors along each side of the pgram as well 637 // as the device space perpendicular dimension across the pgram 638 // (which is different than the lengths of the two pairs of sides 639 // since it is measured perpendicular to the "other" sides, not 640 // along the original sides). 641 // The perpendicular dimension is the dot product of the perpendicular 642 // of the unit vector for one pair of sides with the displacement 643 // vector for the other pair of sides. 644 // The unit vector perpendicular to (dx,dy) is given by: 645 // normx = dx / len(dx, dy); 646 // normy = dy / len(dx, dy); 647 // The (ccw) perpendicular vectors would then be: 648 // unitperpx = +normy = +dy / len; 649 // unitperpy = -normx = -dx / len; 650 // Thus the perpendicular width and height distances are: 651 // pwdist = wvec.uphvec = wvecx * (hvecy/hlen) - wvecy * (hvecx/hlen) 652 // phdist = hvec.upwvec = hvecx * (wvecy/wlen) - hvecy * (wvecx/wlen) 653 // If we factor out the divide by the lengths then we are left 654 // with numerators that are simply negations of each other: 655 // pwdist = (wvecx * hvecy - wvecy * hvecx) / hlen 656 // phdist = (hvecx * wvecy - hvecy * wvecx) / wlen 657 // Finally we multiply by 0.5 since we want the distance to the 658 // edges of the parallelgram from the center point, not across the 659 // whole pgram. And, since we want the absolute value, we can 660 // ignore the fact that the numerator is negated between the two 661 // formulas since we will just have to negate one of the 2 values 662 // afterwards anyway to make them both positive. 663 // Note that the numerators are the formula for the area of the 664 // parallelogram (without the abs() operation). Dividing by the 665 // length of one of the sides would then give the length of the 666 // perpendicular across to the other side. 667 float halfarea = (wvecx * hvecy - wvecy * hvecx) * 0.5f; 668 float pwdist = halfarea / hlen; 669 float phdist = halfarea / wlen; 670 if (pwdist < 0) pwdist = -pwdist; 671 if (phdist < 0) phdist = -phdist; 672 673 // Now we calculate the normalized unit vectors. 674 float nwvecx = wvecx / wlen; 675 float nwvecy = wvecy / wlen; 676 float nhvecx = hvecx / hlen; 677 float nhvecy = hvecy / hlen; 678 679 // Bias the parallelogram corner coordinates "outward" by half a 680 // pixel distance. 681 // The most general way to do this is to move each parallelogram 682 // edge outward by a specified distance and then find the new 683 // intersection point. 684 // The general form for the intersection of 2 lines is: 685 // line1 = (x1,y1) -> (x2,y2) 686 // line2 = (x3,y3) -> (x4,y4) 687 // t = ((x4-x3)(y1-y3) - (y4-y3)(x1-x3)) 688 // / ((y4-y3)(x2-x1) - (x4-x3)(y2-y1)) 689 // intersection point = (x1 + t*(x2-x1), y1 + t*(y2-y1)) 690 // Now consider if these lines are displaced versions of 2 691 // other lines which share a common end point (such as is the 692 // case of pushing 2 adjacent edges of the pgram outward: 693 // line1 = (xa+dx1, ya+dy1) -> (xb+dx1, yb+dy1) 694 // line2 = (xa+dx2, ya+dy2) -> (xc+dx2, yc+dy2) 695 // "x4-x3" = (xc+dx2) - (xa+dx2) = xc-xa 696 // "y4-y3" = (yc+dy2) - (ya+dy2) = yc-ya 697 // "x2-x1" = (xb+dx1) - (xa+dx1) = xb-xa 698 // "y2-y1" = (yb+dy1) - (ya+dy1) = yb-ya 699 // "y1-y3" = (y1+dy1) - (y1+dy2) = dy1 - dy2 700 // "x1-x3" = (xa+dx1) - (xa+dx2) = dx1 - dx2 701 // t = ((xc-xa)(dy1-dy2) - ((yc-ya)(dx1-dx2)) 702 // / ((yc-ya)(xb-xa) - (xc-xa)(yb-ya)) 703 // Now we need to displace these 2 lines "outward" by half a 704 // pixel distance. We will start by looking at applying a unit 705 // pixel distance and then cutting the adjustment in half (it 706 // can be seen to scale linearly when you look at the final 707 // equations). This is achieved by applying one of our unit 708 // vectors with a cw rotation to one line and the other unit 709 // vector with a ccw rotation to the other line. Ideally we 710 // would choose which to apply cw vs. ccw by the way that this 711 // corner of the pgram is turning, but as it turns out, the 712 // consequences of getting it backward is that our "t" will 713 // turn out negative (just negate all of the d[xy][12] values 714 // in the t equation above and you will see that it changes 715 // sign. Since we are calculating the new corner using the 716 // equation newx = (xa+dx1) + t*(xb-xa), we want the value of 717 // t that drives the point "away from xb,yb", in other words, 718 // we want the negative t. So, if t is positive then we want 719 // to use negative t and reverse the perpendicular offset we 720 // applied to xa: 721 // t < 0 => newx = (xa+dx) + t*(xb-xa) = xa + (dx + t*(xb-xa)) 722 // t > 0 => newx = (xa-dx) - t*(xb-xa) = xa - (dx + t*(xb-xa)) 723 // where (copying t equation from above again): 724 // t = ((xc-xa)(dy1-dy2) - ((yc-ya)(dx1-dx2)) 725 // / ((yc-ya)(xb-xa) - (xc-xa)(yb-ya)) 726 // For xa,ya = xUL,yUL and xb,yb = xUR,yUR and xc,yc = xLL,yLL: 727 // [xy]b - [xy]a = [xy]UR - [xy]UL = wvec 728 // [xy]c - [xy]a = [xy]LL - [xy]UL = hvec 729 // dx1,dy1 = +nwvecy, -nwvecx // ccw on xa->xb 730 // dx2,dy2 = -nhvecy, +nhvecx // cw on xa->xc 731 // dx1 - dx2 = +nwvecy - -nhvecy = nwvecy + nhvecy 732 // dy1 - dy2 = -nwvecx - +nhvecx = -(nwvecx + nhvecx) 733 float num = -hvecx*(nwvecx + nhvecx) - hvecy*(nwvecy + nhvecy); 734 float den = hvecy*wvecx - hvecx*wvecy; 735 float t = num / den; 736 // Negating the sign of t and multiplying by 0.5 gets us the 737 // proper sign for the offset and cuts it down to half a pixel 738 float factor = FRINGE_FACTOR * Math.signum(t); 739 float offx = (t * wvecx + nwvecy) * factor; 740 float offy = (t * wvecy - nwvecx) * factor; 741 xUL += offx; yUL += offy; 742 // x22 is offset by the reverse amounts 743 xLR -= offx; yLR -= offy; 744 // For xa,ya = xUR,yUR and xb,yb = xLR,yLR and xc,yc = xUL,yUL 745 // Note that xa,ya => xc,yc is negative of wvec 746 // [xy]b - [xy]a = [xy]LR - [xy]UR = +hvec 747 // [xy]c - [xy]a = [xy]UL - [xy]UR = -wvec 748 // dx1,dy1 = +nhvecy, -nhvecx 749 // dx2,dy2 = -(-nwvecy), +(-nwvecx) = +nwvecy, -nwvecx 750 // dx1 - dx2 = nhvecy - nwvecy 751 // dy1 - dy2 = -nhvecx - -nwvecx = nwvecx - nhvecx 752 // den = -wvecy * hvecx - -wvecx * hvecy 753 // = hvecy * wvecx - hvecx * wvecy (already computed) 754 // num = -wvecx * (nwvecx - nhvecx) - -wvecy * (nhvecy - nwvecy) 755 // = wvecy * (nhvecy - nwvecy) - wvecx * (nwvecx - nhvecx) 756 num = wvecy * (nhvecy - nwvecy) - wvecx * (nwvecx - nhvecx); 757 t = num / den; 758 factor = FRINGE_FACTOR * Math.signum(t); 759 offx = (t * hvecx + nhvecy) * factor; 760 offy = (t * hvecy - nhvecx) * factor; 761 xUR += offx; yUR += offy; 762 xLL -= offx; yLL -= offy; 763 764 // texture coordinates (uv) will be calculated using a transform 765 // by the perpendicular unit vectors so that the texture U 766 // coordinates measure our progress along the pwdist axis (i.e. 767 // perpendicular to hvec and the texture V coordinates measure 768 // our progress along phdist (perpendicular to wvec): 769 // u = x * nhvecy - y * nhvecx; 770 // v = x * nwvecy - y * nwvecx; 771 772 // We now calculate the uv paramters for the 4 corners such that 773 // uv(cx,cy) = 0,0 774 // uv(corners - center) = +/-(wlen/2), +/-(hlen/2) 775 // Note that: 776 // uv(corner - center) = uv(corner) - uv(center) 777 // Calculate the center of the parallelogram and its uv values 778 float xC = (xUL + xLR) * 0.5f; 779 float yC = (yUL + yLR) * 0.5f; 780 float uC = xC * nhvecy - yC * nhvecx; 781 float vC = xC * nwvecy - yC * nwvecx; 782 // Now use that to calculate the corner values relative to the center 783 float uUL = xUL * nhvecy - yUL * nhvecx - uC; 784 float vUL = xUL * nwvecy - yUL * nwvecx - vC; 785 float uUR = xUR * nhvecy - yUR * nhvecx - uC; 786 float vUR = xUR * nwvecy - yUR * nwvecx - vC; 787 float uLL = xLL * nhvecy - yLL * nhvecx - uC; 788 float vLL = xLL * nwvecy - yLL * nwvecx - vC; 789 float uLR = xLR * nhvecy - yLR * nhvecx - uC; 790 float vLR = xLR * nwvecy - yLR * nwvecx - vC; 791 792 // the pgram params have been calculated in device space, so we use 793 // identity transform here 794 if (type == MaskType.DRAW_ROUNDRECT || type == MaskType.FILL_ROUNDRECT) { 795 float oarcw = pwdist * arcfractw; 796 float oarch = phdist * arcfracth; 797 if (oarcw < 0.5 || oarch < 0.5) { 798 // The pgram renderer fades the entire primitive if the arc 799 // sizes fall below 0.5 pixels since the interiors act as if 800 // they are sampled at the center of the indicated circle and 801 // radii smaller than that produce less than full coverage 802 // even at the circle center. If the arcwh are less than 803 // 0.5 then the difference in area of the corner pixels 804 // compared to a PGRAM primitive of the same size is just the 805 // tiny corner cutout of area (4-PI)/16 which is less than 806 // .06 pixels. Thus, we can convert the primitive to a 807 // PGRAM without any loss of precision. 808 type = (type == MaskType.DRAW_ROUNDRECT) 809 ? MaskType.DRAW_PGRAM : MaskType.FILL_PGRAM; 810 } else { 811 float flatw = pwdist - oarcw; 812 float flath = phdist - oarch; 813 float ivalw, ivalh; 814 if (type == MaskType.DRAW_ROUNDRECT) { 815 float iwdist = pwdist * ifractw; 816 float ihdist = phdist * ifracth; 817 // First we calculate the inner arc radii and see if they 818 // are large enough to render. 819 ivalw = iwdist - flatw; 820 ivalh = ihdist - flath; 821 // As above we need to fix things if we get inner arc 822 // radii below half a pixel. We have a special shader 823 // for doing a "semi round" rect which has a round outer 824 // shell and a rectangular (pgram) inner shell... 825 if (ivalw < 0.5f || ivalh < 0.5f) { 826 // inner val is idim 827 ivalw = iwdist; 828 ivalh = ihdist; 829 type = MaskType.DRAW_SEMIROUNDRECT; 830 } else { 831 // inner val is invarcradii 832 ivalw = 1.0f / ivalw; 833 ivalh = 1.0f / ivalh; 834 } 835 } else { 836 // Not used by FILL_ROUNDRECT, but we need constant 837 // values that will not cause an unnecessary vertex 838 // buffer flush in the validateOp below. 839 ivalw = ivalh = 0f; 840 } 841 oarcw = 1.0f / oarcw; 842 oarch = 1.0f / oarch; 843 Shader shader = 844 context.validatePaintOp(this, rendertx, type, 845 rx, ry, rw, rh, 846 oarcw, oarch, 847 ivalw, ivalh, 0, 0); 848 shader.setConstant("oinvarcradii", oarcw, oarch); 849 if (type == MaskType.DRAW_ROUNDRECT) { 850 shader.setConstant("iinvarcradii", ivalw, ivalh); 851 } else if (type == MaskType.DRAW_SEMIROUNDRECT) { 852 shader.setConstant("idim", ivalw, ivalh); 853 } 854 pwdist = flatw; 855 phdist = flath; 856 } 857 } // no else here as we may have converted an RRECT to a PGRAM above 858 if (type == MaskType.DRAW_PGRAM || type == MaskType.DRAW_ELLIPSE) { 859 float idimw = pwdist * ifractw; 860 float idimh = phdist * ifracth; 861 if (type == MaskType.DRAW_ELLIPSE) { 862 if (Math.abs(pwdist - phdist) < .01) { 863 type = MaskType.DRAW_CIRCLE; 864 // The phdist and idimh parameters will not be used by this 865 // shader, but we do need the maximum single pixel coverage 866 // so we coopt them to be min(1.0, area): 867 phdist = (float) Math.min(1.0, phdist * phdist * Math.PI); 868 idimh = (float) Math.min(1.0, idimh * idimh * Math.PI); 869 } else { 870 // the ellipse drawing shader uses inverted arc dimensions 871 // to turn divides into multiplies 872 pwdist = 1.0f / pwdist; 873 phdist = 1.0f / phdist; 874 idimw = 1.0f / idimw; 875 idimh = 1.0f / idimh; 876 } 877 } 878 Shader shader = 879 context.validatePaintOp(this, rendertx, type, 880 rx, ry, rw, rh, 881 idimw, idimh, 0f, 0f, 0f, 0f); 882 shader.setConstant("idim", idimw, idimh); 883 } else if (type == MaskType.FILL_ELLIPSE) { 884 if (Math.abs(pwdist - phdist) < .01) { 885 type = MaskType.FILL_CIRCLE; 886 // The phdist parameter will not be used by this shader, 887 // but we do need the maximum single pixel contribution 888 // so we coopt the value to be min(1.0, area): 889 phdist = (float) Math.min(1.0, phdist * phdist * Math.PI); 890 } else { 891 // the ellipse filling shader uses inverted arc dimensions to 892 // turn divides into multiplies: 893 pwdist = 1.0f / pwdist; 894 phdist = 1.0f / phdist; 895 uUL *= pwdist; vUL *= phdist; 896 uUR *= pwdist; vUR *= phdist; 897 uLL *= pwdist; vLL *= phdist; 898 uLR *= pwdist; vLR *= phdist; 899 } 900 context.validatePaintOp(this, rendertx, type, rx, ry, rw, rh); 901 } else if (type == MaskType.FILL_PGRAM) { 902 context.validatePaintOp(this, rendertx, type, rx, ry, rw, rh); 903 } 904 905 context.getVertexBuffer().addMappedPgram(xUL, yUL, xUR, yUR, 906 xLL, yLL, xLR, yLR, 907 uUL, vUL, uUR, vUR, 908 uLL, vLL, uLR, vLR, 909 pwdist, phdist); 910 } 911 912 AffineBase getPaintTextureTx(BaseTransform renderTx, Shader shader, 913 float rx, float ry, float rw, float rh) 914 { 915 switch (paint.getType()) { 916 case COLOR: 917 return null; 918 case LINEAR_GRADIENT: 919 return PaintHelper.getLinearGradientTx((LinearGradient) paint, 920 shader, renderTx, 921 rx, ry, rw, rh); 922 case RADIAL_GRADIENT: 923 return PaintHelper.getRadialGradientTx((RadialGradient) paint, 924 shader, renderTx, 925 rx, ry, rw, rh); 926 case IMAGE_PATTERN: 927 return PaintHelper.getImagePatternTx(this, (ImagePattern) paint, 928 shader, renderTx, 929 rx, ry, rw, rh); 930 } 931 throw new InternalError("Unrecogized paint type: "+paint); 932 } 933 934 // The second set of rectangular bounds are for validating a 935 // proportional paint. They should be identical to the first 936 // set for a fillRect() operation, but they may be different 937 // for a vertical or horizontal drawLine() operation. 938 boolean fillPrimRect(float x, float y, float w, float h, 939 Texture rectTex, Texture wrapTex, 940 float bx, float by, float bw, float bh) 941 { 942 BaseTransform xform = getTransformNoClone(); 943 float mxx = (float) xform.getMxx(); 944 float mxy = (float) xform.getMxy(); 945 float mxt = (float) xform.getMxt(); 946 float myx = (float) xform.getMyx(); 947 float myy = (float) xform.getMyy(); 948 float myt = (float) xform.getMyt(); 949 float dxdist = len(mxx, myx); 950 float dydist = len(mxy, myy); 951 if (dxdist == 0.0f || dydist == 0.0f) { 952 // entire path has collapsed and occupies no area 953 return true; 954 } 955 float pixelw = 1.0f / dxdist; 956 float pixelh = 1.0f / dydist; 957 float x0 = x - pixelw * 0.5f; 958 float y0 = y - pixelh * 0.5f; 959 float x1 = x + w + pixelw * 0.5f; 960 float y1 = y + h + pixelh * 0.5f; 961 int cellw = (int) Math.ceil(w * dxdist - 1.0f/512.0f); 962 int cellh = (int) Math.ceil(h * dydist - 1.0f/512.0f); 963 VertexBuffer vb = context.getVertexBuffer(); 964 int max = context.getRectTextureMaxSize(); 965 if (cellw <= max && cellh <= max) { 966 float u0 = ((cellw * (cellw + 1)) / 2) - 0.5f; 967 float v0 = ((cellh * (cellh + 1)) / 2) - 0.5f; 968 float u1 = u0 + cellw + 1.0f; 969 float v1 = v0 + cellh + 1.0f; 970 u0 /= rectTex.getPhysicalWidth(); 971 v0 /= rectTex.getPhysicalHeight(); 972 u1 /= rectTex.getPhysicalWidth(); 973 v1 /= rectTex.getPhysicalHeight(); 974 if (xform.isTranslateOrIdentity()) { 975 x0 += mxt; 976 y0 += myt; 977 x1 += mxt; 978 y1 += myt; 979 xform = IDENT; 980 } else if (xform.is2D()) { 981 Shader shader = 982 context.validatePaintOp(this, IDENT, MaskType.ALPHA_TEXTURE, rectTex, 983 bx, by, bw, bh); 984 AffineBase paintTx = getPaintTextureTx(IDENT, shader, bx, by, bw, bh); 985 if (paintTx == null) { 986 vb.addMappedPgram(x0 * mxx + y0 * mxy + mxt, x0 * myx + y0 * myy + myt, 987 x1 * mxx + y0 * mxy + mxt, x1 * myx + y0 * myy + myt, 988 x0 * mxx + y1 * mxy + mxt, x0 * myx + y1 * myy + myt, 989 x1 * mxx + y1 * mxy + mxt, x1 * myx + y1 * myy + myt, 990 u0, v0, u1, v0, u0, v1, u1, v1, 0, 0); 991 } else { 992 vb.addMappedPgram(x0 * mxx + y0 * mxy + mxt, x0 * myx + y0 * myy + myt, 993 x1 * mxx + y0 * mxy + mxt, x1 * myx + y0 * myy + myt, 994 x0 * mxx + y1 * mxy + mxt, x0 * myx + y1 * myy + myt, 995 x1 * mxx + y1 * mxy + mxt, x1 * myx + y1 * myy + myt, 996 u0, v0, u1, v0, u0, v1, u1, v1, 997 x0, y0, x1, y1, paintTx); 998 } 999 return true; 1000 } else { 1001 System.out.println("Not a 2d transform!"); 1002 mxt = myt = 0.0f; 1003 } 1004 Shader shader = 1005 context.validatePaintOp(this, xform, MaskType.ALPHA_TEXTURE, rectTex, 1006 bx, by, bw, bh); 1007 AffineBase paintTx = getPaintTextureTx(IDENT, shader, bx, by, bw, bh); 1008 if (paintTx == null) { 1009 vb.addQuad(x0, y0, x1, y1, 1010 u0, v0, u1, v1); 1011 } else { 1012 paintTx.translate(-mxt, -myt); 1013 vb.addQuad(x0, y0, x1, y1, 1014 u0, v0, u1, v1, 1015 paintTx); 1016 } 1017 return true; 1018 } 1019 if (wrapTex == null) { 1020 return false; 1021 } 1022 float u0 = 0.5f / wrapTex.getPhysicalWidth(); 1023 float v0 = 0.5f / wrapTex.getPhysicalHeight(); 1024 float uc = (cellw * 0.5f + 1.0f) / wrapTex.getPhysicalWidth(); 1025 float vc = (cellh * 0.5f + 1.0f) / wrapTex.getPhysicalHeight(); 1026 float xc = x + w * 0.5f; 1027 float yc = y + h * 0.5f; 1028 if (xform.isTranslateOrIdentity()) { 1029 x0 += mxt; 1030 y0 += myt; 1031 xc += mxt; 1032 yc += myt; 1033 x1 += mxt; 1034 y1 += myt; 1035 xform = IDENT; 1036 } else if (xform.is2D()) { 1037 Shader shader = 1038 context.validatePaintOp(this, IDENT, MaskType.ALPHA_TEXTURE, wrapTex, 1039 bx, by, bw, bh); 1040 AffineBase paintTx = getPaintTextureTx(IDENT, shader, bx, by, bw, bh); 1041 float mxx_x0 = mxx * x0, myx_x0 = myx * x0; 1042 float mxy_y0 = mxy * y0, myy_y0 = myy * y0; 1043 float mxx_xc = mxx * xc, myx_xc = myx * xc; 1044 float mxy_yc = mxy * yc, myy_yc = myy * yc; 1045 float mxx_x1 = mxx * x1, myx_x1 = myx * x1; 1046 float mxy_y1 = mxy * y1, myy_y1 = myy * y1; 1047 // xcc,ycc used in all 4 quads 1048 float xcc = mxx_xc + mxy_yc + mxt; 1049 float ycc = myx_xc + myy_yc + myt; 1050 // xcn, ycn and xnc, ync all used in 2 quads each 1051 float xc0 = mxx_xc + mxy_y0 + mxt; 1052 float yc0 = myx_xc + myy_y0 + myt; 1053 float x0c = mxx_x0 + mxy_yc + mxt; 1054 float y0c = myx_x0 + myy_yc + myt; 1055 float xc1 = mxx_xc + mxy_y1 + mxt; 1056 float yc1 = myx_xc + myy_y1 + myt; 1057 float x1c = mxx_x1 + mxy_yc + mxt; 1058 float y1c = myx_x1 + myy_yc + myt; 1059 // Note that all quads use same 00->c0->0c->cc coordinates for 1060 // the inner and outer uv texture coordinates regardless of the 1061 // reflection of the quad of vertex coordinates 1062 1063 if (paintTx == null) { 1064 // quad1 - 00 -> c0 -> 0c -> cc 1065 vb.addMappedPgram(x0 * mxx + y0 * mxy + mxt, x0 * myx + y0 * myy + myt, 1066 xc0, yc0, x0c, y0c, xcc, ycc, 1067 u0, v0, uc, v0, u0, vc, uc, vc, 0, 0); 1068 // quad2 - 10 -> c0 -> 1c -> cc (reflect quad1 around x=c) 1069 vb.addMappedPgram(x1 * mxx + y0 * mxy + mxt, x1 * myx + y0 * myy + myt, 1070 xc0, yc0, x1c, y1c, xcc, ycc, 1071 u0, v0, uc, v0, u0, vc, uc, vc, 0, 0); 1072 // quad3 - 01 -> c1 -> 0c -> cc (reflect quad1 around y=c) 1073 vb.addMappedPgram(x0 * mxx + y1 * mxy + mxt, x0 * myx + y1 * myy + myt, 1074 xc1, yc1, x0c, y0c, xcc, ycc, 1075 u0, v0, uc, v0, u0, vc, uc, vc, 0, 0); 1076 // quad4 - 11 -> c1 -> 1c -> cc (reflect quad1 around x=c and y=c) 1077 vb.addMappedPgram(x1 * mxx + y1 * mxy + mxt, x1 * myx + y1 * myy + myt, 1078 xc1, yc1, x1c, y1c, xcc, ycc, 1079 u0, v0, uc, v0, u0, vc, uc, vc, 0, 0); 1080 } else { 1081 // quad1 - 00 -> c0 -> 0c -> cc 1082 vb.addMappedPgram(x0 * mxx + y0 * mxy + mxt, x0 * myx + y0 * myy + myt, 1083 xc0, yc0, x0c, y0c, xcc, ycc, 1084 u0, v0, uc, v0, u0, vc, uc, vc, 1085 x0, y0, xc, yc, paintTx); 1086 // quad2 - 10 -> c0 -> 1c -> cc (reflect quad1 around x=c) 1087 vb.addMappedPgram(x1 * mxx + y0 * mxy + mxt, x1 * myx + y0 * myy + myt, 1088 xc0, yc0, x1c, y1c, xcc, ycc, 1089 u0, v0, uc, v0, u0, vc, uc, vc, 1090 x1, y0, xc, yc, paintTx); 1091 // quad3 - 01 -> c1 -> 0c -> cc (reflect quad1 around y=c) 1092 vb.addMappedPgram(x0 * mxx + y1 * mxy + mxt, x0 * myx + y1 * myy + myt, 1093 xc1, yc1, x0c, y0c, xcc, ycc, 1094 u0, v0, uc, v0, u0, vc, uc, vc, 1095 x0, y1, xc, yc, paintTx); 1096 // quad4 - 11 -> c1 -> 1c -> cc (reflect quad1 around x=c and y=c) 1097 vb.addMappedPgram(x1 * mxx + y1 * mxy + mxt, x1 * myx + y1 * myy + myt, 1098 xc1, yc1, x1c, y1c, xcc, ycc, 1099 u0, v0, uc, v0, u0, vc, uc, vc, 1100 x1, y1, xc, yc, paintTx); 1101 } 1102 return true; 1103 } else { 1104 System.out.println("Not a 2d transform!"); 1105 mxt = myt = 0; 1106 } 1107 Shader shader = 1108 context.validatePaintOp(this, xform, MaskType.ALPHA_TEXTURE, wrapTex, 1109 bx, by, bw, bh); 1110 AffineBase paintTx = getPaintTextureTx(IDENT, shader, bx, by, bw, bh); 1111 if (paintTx != null) { 1112 paintTx.translate(-mxt, -myt); 1113 } 1114 vb.addQuad(x0, y0, xc, yc, 1115 u0, v0, uc, vc, 1116 paintTx); 1117 vb.addQuad(x1, y0, xc, yc, 1118 u0, v0, uc, vc, 1119 paintTx); 1120 vb.addQuad(x0, y1, xc, yc, 1121 u0, v0, uc, vc, 1122 paintTx); 1123 vb.addQuad(x1, y1, xc, yc, 1124 u0, v0, uc, vc, 1125 paintTx); 1126 return true; 1127 } 1128 1129 boolean drawPrimRect(float x, float y, float w, float h) { 1130 float lw = stroke.getLineWidth(); 1131 float pad = getStrokeExpansionFactor(stroke) * lw; 1132 BaseTransform xform = getTransformNoClone(); 1133 float mxx = (float) xform.getMxx(); 1134 float mxy = (float) xform.getMxy(); 1135 float mxt = (float) xform.getMxt(); 1136 float myx = (float) xform.getMyx(); 1137 float myy = (float) xform.getMyy(); 1138 float myt = (float) xform.getMyt(); 1139 float dxdist = len(mxx, myx); 1140 float dydist = len(mxy, myy); 1141 if (dxdist == 0.0f || dydist == 0.0f) { 1142 // entire path has collapsed and occupies no area 1143 return true; 1144 } 1145 float pixelw = 1.0f / dxdist; 1146 float pixelh = 1.0f / dydist; 1147 float x0 = x - pad - pixelw * 0.5f; 1148 float y0 = y - pad - pixelh * 0.5f; 1149 float xc = x + w * 0.5f; 1150 float yc = y + h * 0.5f; 1151 float x1 = x + w + pad + pixelw * 0.5f; 1152 float y1 = y + h + pad + pixelh * 0.5f; 1153 Texture rTex = context.getWrapRectTexture(); 1154 float wscale = 1.0f / rTex.getPhysicalWidth(); 1155 float hscale = 1.0f / rTex.getPhysicalHeight(); 1156 float ou0 = 0.5f * wscale; 1157 float ov0 = 0.5f * hscale; 1158 float ouc = ((w * 0.5f + pad) * dxdist + 1.0f) * wscale; 1159 float ovc = ((h * 0.5f + pad) * dydist + 1.0f) * hscale; 1160 float offsetx = lw * dxdist * wscale; 1161 float offsety = lw * dydist * hscale; 1162 VertexBuffer vb = context.getVertexBuffer(); 1163 if (xform.isTranslateOrIdentity()) { 1164 x0 += mxt; 1165 y0 += myt; 1166 xc += mxt; 1167 yc += myt; 1168 x1 += mxt; 1169 y1 += myt; 1170 xform = IDENT; 1171 } else if (xform.is2D()) { 1172 Shader shader = 1173 context.validatePaintOp(this, IDENT, MaskType.ALPHA_TEXTURE_DIFF, 1174 rTex, x, y, w, h, 1175 offsetx, offsety, 0, 0, 0, 0); 1176 shader.setConstant("innerOffset", offsetx, offsety); 1177 AffineBase paintTx = getPaintTextureTx(IDENT, shader, x, y, w, h); 1178 float mxx_x0 = mxx * x0, myx_x0 = myx * x0; 1179 float mxy_y0 = mxy * y0, myy_y0 = myy * y0; 1180 float mxx_xc = mxx * xc, myx_xc = myx * xc; 1181 float mxy_yc = mxy * yc, myy_yc = myy * yc; 1182 float mxx_x1 = mxx * x1, myx_x1 = myx * x1; 1183 float mxy_y1 = mxy * y1, myy_y1 = myy * y1; 1184 1185 // xcc,ycc used in all 4 quads 1186 float xcc = mxx_xc + mxy_yc + mxt; 1187 float ycc = myx_xc + myy_yc + myt; 1188 // xcn, ycn and xnc, ync all used in 2 quads each 1189 float xc0 = mxx_xc + mxy_y0 + mxt; 1190 float yc0 = myx_xc + myy_y0 + myt; 1191 float x0c = mxx_x0 + mxy_yc + mxt; 1192 float y0c = myx_x0 + myy_yc + myt; 1193 float xc1 = mxx_xc + mxy_y1 + mxt; 1194 float yc1 = myx_xc + myy_y1 + myt; 1195 float x1c = mxx_x1 + mxy_yc + mxt; 1196 float y1c = myx_x1 + myy_yc + myt; 1197 // Note that all quads use same 00->c0->0c->cc coordinates for 1198 // the inner and outer uv texture coordinates regardless of the 1199 // reflection of the quad of vertex coordinates 1200 1201 if (paintTx == null) { 1202 // quad1 - 00 -> c0 -> 0c -> cc 1203 vb.addMappedPgram(mxx_x0 + mxy_y0 + mxt, myx_x0 + myy_y0 + myt, 1204 xc0, yc0, x0c, y0c, xcc, ycc, 1205 ou0, ov0, ouc, ov0, ou0, ovc, ouc, ovc, 1206 0, 0); 1207 // quad2 - 10 -> c0 -> 1c -> cc (reflect quad1 around x=c) 1208 vb.addMappedPgram(mxx_x1 + mxy_y0 + mxt, myx_x1 + myy_y0 + myt, 1209 xc0, yc0, x1c, y1c, xcc, ycc, 1210 ou0, ov0, ouc, ov0, ou0, ovc, ouc, ovc, 1211 0, 0); 1212 // quad3 - 01 -> c1 -> 0c -> cc (reflect quad1 around y=c) 1213 vb.addMappedPgram(mxx_x0 + mxy_y1 + mxt, myx_x0 + myy_y1 + myt, 1214 xc1, yc1, x0c, y0c, xcc, ycc, 1215 ou0, ov0, ouc, ov0, ou0, ovc, ouc, ovc, 1216 0, 0); 1217 // quad4 - 11 -> c1 -> 1c -> cc (reflect quad1 around x=c and y=c) 1218 vb.addMappedPgram(mxx_x1 + mxy_y1 + mxt, myx_x1 + myy_y1 + myt, 1219 xc1, yc1, x1c, y1c, xcc, ycc, 1220 ou0, ov0, ouc, ov0, ou0, ovc, ouc, ovc, 1221 0, 0); 1222 } else { 1223 // quad1 - 00 -> c0 -> 0c -> cc 1224 vb.addMappedPgram(mxx_x0 + mxy_y0 + mxt, myx_x0 + myy_y0 + myt, 1225 xc0, yc0, x0c, y0c, xcc, ycc, 1226 ou0, ov0, ouc, ov0, ou0, ovc, ouc, ovc, 1227 x0, y0, xc, yc, paintTx); 1228 // quad2 - 10 -> c0 -> 1c -> cc (reflect quad1 around x=c) 1229 vb.addMappedPgram(mxx_x1 + mxy_y0 + mxt, myx_x1 + myy_y0 + myt, 1230 xc0, yc0, x1c, y1c, xcc, ycc, 1231 ou0, ov0, ouc, ov0, ou0, ovc, ouc, ovc, 1232 x1, y0, xc, yc, paintTx); 1233 // quad3 - 01 -> c1 -> 0c -> cc (reflect quad1 around y=c) 1234 vb.addMappedPgram(mxx_x0 + mxy_y1 + mxt, myx_x0 + myy_y1 + myt, 1235 xc1, yc1, x0c, y0c, xcc, ycc, 1236 ou0, ov0, ouc, ov0, ou0, ovc, ouc, ovc, 1237 x0, y1, xc, yc, paintTx); 1238 // quad4 - 11 -> c1 -> 1c -> cc (reflect quad1 around x=c and y=c) 1239 vb.addMappedPgram(mxx_x1 + mxy_y1 + mxt, myx_x1 + myy_y1 + myt, 1240 xc1, yc1, x1c, y1c, xcc, ycc, 1241 ou0, ov0, ouc, ov0, ou0, ovc, ouc, ovc, 1242 x1, y1, xc, yc, paintTx); 1243 } 1244 rTex.unlock(); 1245 return true; 1246 } else { 1247 System.out.println("Not a 2d transform!"); 1248 mxt = myt = 0.0f; 1249 } 1250 Shader shader = 1251 context.validatePaintOp(this, xform, MaskType.ALPHA_TEXTURE_DIFF, 1252 rTex, x, y, w, h, 1253 offsetx, offsety, 0, 0, 0, 0); 1254 shader.setConstant("innerOffset", offsetx, offsety); 1255 AffineBase paintTx = getPaintTextureTx(IDENT, shader, x, y, w, h); 1256 if (paintTx != null) { 1257 paintTx.translate(-mxt, -myt); 1258 } 1259 vb.addQuad( x0, y0, xc, yc, 1260 ou0, ov0, ouc, ovc, 1261 paintTx); 1262 vb.addQuad( x1, y0, xc, yc, 1263 ou0, ov0, ouc, ovc, 1264 paintTx); 1265 vb.addQuad( x0, y1, xc, yc, 1266 ou0, ov0, ouc, ovc, 1267 paintTx); 1268 vb.addQuad( x1, y1, xc, yc, 1269 ou0, ov0, ouc, ovc, 1270 paintTx); 1271 rTex.unlock(); 1272 return true; 1273 } 1274 1275 boolean drawPrimDiagonal(float x1, float y1, float x2, float y2, 1276 float lw, int cap, 1277 float bx, float by, float bw, float bh) 1278 { 1279 // assert x1 != x2 && y1 != y2, otherwise caller would have 1280 // vectored us to fillPrimRect() 1281 if (stroke.getType() == BasicStroke.TYPE_CENTERED) { 1282 lw *= 0.5f; 1283 } 1284 float dx = x2 - x1; 1285 float dy = y2 - y1; 1286 float len = len(dx, dy); 1287 dx /= len; 1288 dy /= len; 1289 float ldx = dx * lw; 1290 float ldy = dy * lw; 1291 // First expand perpendicularly using (ldy, -ldx) 1292 float xUL = x1 + ldy, yUL = y1 - ldx; 1293 float xUR = x2 + ldy, yUR = y2 - ldx; 1294 float xLL = x1 - ldy, yLL = y1 + ldx; 1295 float xLR = x2 - ldy, yLR = y2 + ldx; 1296 if (cap == BasicStroke.CAP_SQUARE) { 1297 // Then add SQUARE end caps using (ldx, ldy) if needed 1298 xUL -= ldx; yUL -= ldy; 1299 xLL -= ldx; yLL -= ldy; 1300 xUR += ldx; yUR += ldy; 1301 xLR += ldx; yLR += ldy; 1302 } 1303 1304 float hdx, hdy, vdx, vdy; 1305 int cellw, cellh; 1306 BaseTransform xform = getTransformNoClone(); 1307 float mxt = (float) xform.getMxt(); 1308 float myt = (float) xform.getMyt(); 1309 if (xform.isTranslateOrIdentity()) { 1310 hdx = dx; hdy = dy; 1311 vdx = dy; vdy = -dx; 1312 cellw = (int) Math.ceil(len(xUR - xUL, yUR - yUL)); 1313 cellh = (int) Math.ceil(len(xLL - xUL, yLL - yUL)); 1314 xform = IDENT; 1315 } else if (xform.is2D()) { 1316 float mxx = (float) xform.getMxx(); 1317 float mxy = (float) xform.getMxy(); 1318 float myx = (float) xform.getMyx(); 1319 float myy = (float) xform.getMyy(); 1320 float tx, ty; 1321 tx = mxx * xUL + mxy * yUL; 1322 ty = myx * xUL + myy * yUL; 1323 xUL = tx; yUL = ty; 1324 tx = mxx * xUR + mxy * yUR; 1325 ty = myx * xUR + myy * yUR; 1326 xUR = tx; yUR = ty; 1327 tx = mxx * xLL + mxy * yLL; 1328 ty = myx * xLL + myy * yLL; 1329 xLL = tx; yLL = ty; 1330 tx = mxx * xLR + mxy * yLR; 1331 ty = myx * xLR + myy * yLR; 1332 xLR = tx; yLR = ty; 1333 // hdx, hdy are transformed unit vectors along the line 1334 hdx = mxx * dx + mxy * dy; 1335 hdy = myx * dx + myy * dy; 1336 float dlen = len(hdx, hdy); 1337 if (dlen == 0.0f) return true; 1338 hdx /= dlen; 1339 hdy /= dlen; 1340 // vdx, vdy are transformed perpendicular unit vectors 1341 // (perpendicular to the line in user space, but then transformed) 1342 vdx = mxx * dy - mxy * dx; 1343 vdy = myx * dy - myy * dx; 1344 dlen = len(vdx, vdy); 1345 if (dlen == 0.0f) return true; 1346 vdx /= dlen; 1347 vdy /= dlen; 1348 cellw = (int) Math.ceil(Math.abs((xUR - xUL) * hdx + (yUR - yUL) * hdy)); 1349 cellh = (int) Math.ceil(Math.abs((xLL - xUL) * vdx + (yLL - yUL) * vdy)); 1350 xform = IDENT; 1351 } else { 1352 System.out.println("Not a 2d transform!"); 1353 return false; 1354 } 1355 hdx *= 0.5f; 1356 hdy *= 0.5f; 1357 vdx *= 0.5f; 1358 vdy *= 0.5f; 1359 xUL = xUL + mxt + vdx - hdx; 1360 yUL = yUL + myt + vdy - hdy; 1361 xUR = xUR + mxt + vdx + hdx; 1362 yUR = yUR + myt + vdy + hdy; 1363 xLL = xLL + mxt - vdx - hdx; 1364 yLL = yLL + myt - vdy - hdy; 1365 xLR = xLR + mxt - vdx + hdx; 1366 yLR = yLR + myt - vdy + hdy; 1367 VertexBuffer vb = context.getVertexBuffer(); 1368 int cellmax = context.getRectTextureMaxSize(); 1369 if (cellh <= cellmax) { 1370 float v0 = ((cellh * (cellh + 1)) / 2) - 0.5f; 1371 float v1 = v0 + cellh + 1.0f; 1372 Texture rTex = context.getRectTexture(); 1373 v0 /= rTex.getPhysicalHeight(); 1374 v1 /= rTex.getPhysicalHeight(); 1375 if (cellw <= cellmax) { 1376 float u0 = ((cellw * (cellw + 1)) / 2) - 0.5f; 1377 float u1 = u0 + cellw + 1.0f; 1378 u0 /= rTex.getPhysicalWidth(); 1379 u1 /= rTex.getPhysicalWidth(); 1380 context.validatePaintOp(this, xform, MaskType.ALPHA_TEXTURE, rTex, 1381 bx, by, bw, bh); 1382 vb.addMappedPgram(xUL, yUL, xUR, yUR, xLL, yLL, xLR, yLR, 1383 u0, v0, u1, v0, u0, v1, u1, v1, 1384 0, 0); 1385 // System.out.print("1"); System.out.flush(); 1386 rTex.unlock(); 1387 return true; 1388 } 1389 // long thin line (cellw is along the line, cellh is across it) 1390 if (cellw <= cellmax * 2 - 1) { 1391 // 2-slice the line at its midpoint. 1392 // use the cellmax,cellh cell for maximum coverage of each half 1393 // we can use at most (cellmax-0.5) per half so that we do not 1394 // see the antialias drop off on the last half pixel of the far 1395 // end of the cell. This lets us support a line of "length" up 1396 // to (cellmax-0.5 + cellmax-0.5) or (cellmax*2-1). 1397 float xUC = (xUL + xUR) * 0.5f; 1398 float yUC = (yUL + yUR) * 0.5f; 1399 float xLC = (xLL + xLR) * 0.5f; 1400 float yLC = (yLL + yLR) * 0.5f; 1401 float u0 = ((cellmax * (cellmax + 1)) / 2) - 0.5f; 1402 float u1 = u0 + 0.5f + cellw * 0.5f; 1403 u0 /= rTex.getPhysicalWidth(); 1404 u1 /= rTex.getPhysicalWidth(); 1405 context.validatePaintOp(this, xform, MaskType.ALPHA_TEXTURE, rTex, 1406 bx, by, bw, bh); 1407 // first half of line x1,y1 -> midpoint 1408 vb.addMappedPgram(xUL, yUL, xUC, yUC, xLL, yLL, xLC, yLC, 1409 u0, v0, u1, v0, u0, v1, u1, v1, 1410 0, 0); 1411 // second half of line midpoint -> x2,y2 1412 vb.addMappedPgram(xUR, yUR, xUC, yUC, xLR, yLR, xLC, yLC, 1413 u0, v0, u1, v0, u0, v1, u1, v1, 1414 0, 0); 1415 // System.out.print("2"); System.out.flush(); 1416 rTex.unlock(); 1417 return true; 1418 } 1419 // Finally, 3-slice the line (left edge, huge middle, right edge) 1420 float u0 = 0.5f / rTex.getPhysicalWidth(); 1421 float u1 = 1.5f / rTex.getPhysicalWidth(); 1422 // The lower case L or R indicates "inner left" or "inner right" 1423 hdx *= 2.0f; 1424 hdy *= 2.0f; 1425 float xUl = xUL + hdx; 1426 float yUl = yUL + hdy; 1427 float xUr = xUR - hdx; 1428 float yUr = yUR - hdy; 1429 float xLl = xLL + hdx; 1430 float yLl = yLL + hdy; 1431 float xLr = xLR - hdx; 1432 float yLr = yLR - hdy; 1433 context.validatePaintOp(this, xform, MaskType.ALPHA_TEXTURE, rTex, 1434 bx, by, bw, bh); 1435 // first pixel of line x1,y1 -> x1,y1+pixel 1436 vb.addMappedPgram(xUL, yUL, xUl, yUl, xLL, yLL, xLl, yLl, 1437 u0, v0, u1, v0, u0, v1, u1, v1, 1438 0, 0); 1439 // middle part of line x1,y1+pixel -> x2,y2-pixel 1440 vb.addMappedPgram(xUl, yUl, xUr, yUr, xLl, yLl, xLr, yLr, 1441 u1, v0, u1, v0, u1, v1, u1, v1, 1442 0, 0); 1443 // last part of line x2,y2-pixel -> x2,y2 1444 vb.addMappedPgram(xUr, yUr, xUR, yUR, xLr, yLr, xLR, yLR, 1445 u1, v0, u0, v0, u1, v1, u0, v1, 1446 0, 0); 1447 // System.out.print("3"); System.out.flush(); 1448 rTex.unlock(); 1449 return true; 1450 } 1451 // we could 2 and 3 slice extremely wide short lines, but they 1452 // are very rare in practice so we just jump straight to a 1453 // standard 4-slice off of the wrap-rect texture 1454 float xUC = (xUL + xUR) * 0.5f; 1455 float yUC = (yUL + yUR) * 0.5f; 1456 float xLC = (xLL + xLR) * 0.5f; 1457 float yLC = (yLL + yLR) * 0.5f; 1458 float xCL = (xUL + xLL) * 0.5f; 1459 float yCL = (yUL + yLL) * 0.5f; 1460 float xCR = (xUR + xLR) * 0.5f; 1461 float yCR = (yUR + yLR) * 0.5f; 1462 float xCC = (xUC + xLC) * 0.5f; 1463 float yCC = (yUC + yLC) * 0.5f; 1464 Texture rTex = context.getWrapRectTexture(); 1465 float u0 = 0.5f / rTex.getPhysicalWidth(); 1466 float v0 = 0.5f / rTex.getPhysicalHeight(); 1467 float uc = (cellw * 0.5f + 1.0f) / rTex.getPhysicalWidth(); 1468 float vc = (cellh * 0.5f + 1.0f) / rTex.getPhysicalHeight(); 1469 context.validatePaintOp(this, xform, MaskType.ALPHA_TEXTURE, rTex, 1470 bx, by, bw, bh); 1471 vb.addMappedPgram(xUL, yUL, xUC, yUC, xCL, yCL, xCC, yCC, 1472 u0, v0, uc, v0, u0, vc, uc, vc, 1473 0, 0); 1474 vb.addMappedPgram(xUR, yUR, xUC, yUC, xCR, yCR, xCC, yCC, 1475 u0, v0, uc, v0, u0, vc, uc, vc, 1476 0, 0); 1477 vb.addMappedPgram(xLL, yLL, xLC, yLC, xCL, yCL, xCC, yCC, 1478 u0, v0, uc, v0, u0, vc, uc, vc, 1479 0, 0); 1480 vb.addMappedPgram(xLR, yLR, xLC, yLC, xCR, yCR, xCC, yCC, 1481 u0, v0, uc, v0, u0, vc, uc, vc, 1482 0, 0); 1483 // System.out.print("4"); System.out.flush(); 1484 rTex.unlock(); 1485 return true; 1486 } 1487 1488 public void fillRect(float x, float y, float w, float h) { 1489 if (w <= 0 || h <= 0) { 1490 return; 1491 } 1492 if (isComplexPaint) { 1493 scratchRRect.setRoundRect(x, y, w, h, 0, 0); 1494 renderWithComplexPaint(scratchRRect, null, x, y, w, h); 1495 return; 1496 } 1497 if (PrismSettings.primTextureSize != 0) { 1498 Texture rTex = context.getRectTexture(); 1499 Texture wTex = context.getWrapRectTexture(); 1500 boolean success = fillPrimRect(x, y, w, h, rTex, wTex, x, y, w, h); 1501 rTex.unlock(); 1502 wTex.unlock(); 1503 if (success) return; 1504 } 1505 renderGeneralRoundedRect(x, y, w, h, 0f, 0f, 1506 MaskType.FILL_PGRAM, null); 1507 } 1508 1509 public void fillEllipse(float x, float y, float w, float h) { 1510 if (w <= 0 || h <= 0) { 1511 return; 1512 } 1513 if (isComplexPaint) { 1514 scratchEllipse.setFrame(x, y, w, h); 1515 renderWithComplexPaint(scratchEllipse, null, x, y, w, h); 1516 return; 1517 } 1518 if (PrismSettings.primTextureSize != 0) { 1519 if (fillPrimRect(x, y, w, h, 1520 context.getOvalTexture(), 1521 null, 1522 x, y, w, h)) 1523 { 1524 return; 1525 } 1526 } 1527 renderGeneralRoundedRect(x, y, w, h, w, h, 1528 MaskType.FILL_ELLIPSE, null); 1529 } 1530 1531 public void fillRoundRect(float x, float y, float w, float h, 1532 float arcw, float arch) 1533 { 1534 arcw = Math.min(Math.abs(arcw), w); 1535 arch = Math.min(Math.abs(arch), h); 1536 1537 if (w <= 0 || h <= 0) { 1538 return; 1539 } 1540 if (isComplexPaint) { 1541 scratchRRect.setRoundRect(x, y, w, h, arcw, arch); 1542 renderWithComplexPaint(scratchRRect, null, x, y, w, h); 1543 return; 1544 } 1545 renderGeneralRoundedRect(x, y, w, h, arcw, arch, 1546 MaskType.FILL_ROUNDRECT, null); 1547 } 1548 1549 public void fillQuad(float x1, float y1, float x2, float y2) { 1550 float bx, by, bw, bh; 1551 if (x1 <= x2) { 1552 bx = x1; 1553 bw = x2 - x1; 1554 } else { 1555 bx = x2; 1556 bw = x1 - x2; 1557 } 1558 if (y1 <= y2) { 1559 by = y1; 1560 bh = y2 - y1; 1561 } else { 1562 by = y2; 1563 bh = y1 - y2; 1564 } 1565 1566 if (isComplexPaint) { 1567 scratchRRect.setRoundRect(bx, by, bw, bh, 0, 0); 1568 renderWithComplexPaint(scratchRRect, null, bx, by, bw, bh); 1569 return; 1570 } 1571 1572 BaseTransform xform = getTransformNoClone(); 1573 if (PrismSettings.primTextureSize != 0) { 1574 float mxt, myt; 1575 if (xform.isTranslateOrIdentity()) { 1576 mxt = (float) xform.getMxt(); 1577 myt = (float) xform.getMyt(); 1578 xform = IDENT; 1579 x1 += mxt; 1580 y1 += myt; 1581 x2 += mxt; 1582 y2 += myt; 1583 } else { 1584 mxt = myt = 0.0f; 1585 } 1586 Shader shader = 1587 context.validatePaintOp(this, xform, MaskType.ALPHA_ONE, null, 1588 bx, by, bw, bh); 1589 AffineBase paintTx = getPaintTextureTx(IDENT, shader, bx, by, bw, bh); 1590 if (paintTx != null) { 1591 paintTx.translate(-mxt, -myt); 1592 } 1593 context.getVertexBuffer().addQuad(x1, y1, x2, y2, 0, 0, 0, 0, paintTx); 1594 return; 1595 } 1596 if (isSimpleTranslate) { 1597 xform = IDENT; 1598 bx += transX; 1599 by += transY; 1600 } 1601 context.validatePaintOp(this, xform, MaskType.SOLID, bx, by, bw, bh); 1602 1603 VertexBuffer vb = context.getVertexBuffer(); 1604 vb.addQuad(bx, by, bx+bw, by+bh); 1605 } 1606 1607 private static final double SQRT_2 = Math.sqrt(2.0); 1608 private static boolean canUseStrokeShader(BasicStroke bs) { 1609 // RT-27378 1610 // TODO: Expand the cases that renderGeneralRoundRect() can handle... 1611 return (!bs.isDashed() && 1612 (bs.getType() == BasicStroke.TYPE_INNER || 1613 bs.getLineJoin() == BasicStroke.JOIN_ROUND || 1614 (bs.getLineJoin() == BasicStroke.JOIN_MITER && 1615 bs.getMiterLimit() >= SQRT_2))); 1616 } 1617 1618 public void blit(RTTexture srcTex, RTTexture dstTex, 1619 int srcX0, int srcY0, int srcX1, int srcY1, 1620 int dstX0, int dstY0, int dstX1, int dstY1) { 1621 if (dstTex == null) { 1622 context.setRenderTarget(this); 1623 } else { 1624 context.setRenderTarget((BaseGraphics)dstTex.createGraphics()); 1625 } 1626 context.blit(srcTex, dstTex, srcX0, srcY0, srcX1, srcY1, 1627 dstX0, dstY0, dstX1, dstY1); 1628 } 1629 1630 public void drawRect(float x, float y, float w, float h) { 1631 if (w < 0 || h < 0) { 1632 return; 1633 } 1634 if (w == 0 || h == 0) { 1635 drawLine(x, y, x + w, y + h); 1636 return; 1637 } 1638 if (isComplexPaint) { 1639 scratchRRect.setRoundRect(x, y, w, h, 0, 0); 1640 renderWithComplexPaint(scratchRRect, stroke, x, y, w, h); 1641 return; 1642 } 1643 if (canUseStrokeShader(stroke)) { 1644 if (PrismSettings.primTextureSize != 0 && 1645 stroke.getLineJoin() != BasicStroke.CAP_ROUND) 1646 { 1647 if (drawPrimRect(x, y, w, h)) { 1648 return; 1649 } 1650 } 1651 renderGeneralRoundedRect(x, y, w, h, 0f, 0f, 1652 MaskType.DRAW_PGRAM, stroke); 1653 return; 1654 } 1655 scratchRRect.setRoundRect(x, y, w, h, 0, 0); 1656 renderShape(scratchRRect, stroke, x, y, w, h); 1657 } 1658 1659 private boolean checkInnerCurvature(float arcw, float arch) { 1660 // Test to see if inner ellipse satisfies (flattening < 0.5) 1661 // otherwise it will not be approximated by a "parallel ellipse" 1662 // very well and we should just use shape rendering. 1663 1664 // RT-27378 1665 // TODO: Implement better "distance to ellipse" formulas in the shaders 1666 float inset = stroke.getLineWidth() * 1667 (1f - getStrokeExpansionFactor(stroke)); 1668 arcw -= inset; 1669 arch -= inset; 1670 // Note that if either inset arcw,h go to <= 0 then we will invoke the 1671 // fill primitive for ellipse, or the round rect primitive will 1672 // invoke its "tiny inner corner" fixes and we will be safe 1673 return (arcw <= 0 || arch <= 0 || 1674 (arcw * 2f > arch && arch * 2f > arcw)); 1675 } 1676 1677 public void drawEllipse(float x, float y, float w, float h) { 1678 if (w < 0 || h < 0) { 1679 return; 1680 } 1681 if (!isComplexPaint && !stroke.isDashed() && 1682 checkInnerCurvature(w, h)) 1683 { 1684 renderGeneralRoundedRect(x, y, w, h, w, h, 1685 MaskType.DRAW_ELLIPSE, stroke); 1686 return; 1687 } 1688 scratchEllipse.setFrame(x, y, w, h); 1689 renderShape(scratchEllipse, stroke, x, y, w, h); 1690 } 1691 1692 public void drawRoundRect(float x, float y, float w, float h, 1693 float arcw, float arch) 1694 { 1695 arcw = Math.min(Math.abs(arcw), w); 1696 arch = Math.min(Math.abs(arch), h); 1697 1698 if (w < 0 || h < 0) { 1699 return; 1700 } 1701 if (!isComplexPaint && !stroke.isDashed() && 1702 checkInnerCurvature(arcw, arch)) 1703 { 1704 renderGeneralRoundedRect(x, y, w, h, arcw, arch, 1705 MaskType.DRAW_ROUNDRECT, stroke); 1706 return; 1707 } 1708 scratchRRect.setRoundRect(x, y, w, h, arcw, arch); 1709 renderShape(scratchRRect, stroke, x, y, w, h); 1710 } 1711 1712 public void drawLine(float x1, float y1, float x2, float y2) { 1713 float bx, by, bw, bh; 1714 if (x1 <= x2) { 1715 bx = x1; 1716 bw = x2 - x1; 1717 } else { 1718 bx = x2; 1719 bw = x1 - x2; 1720 } 1721 if (y1 <= y2) { 1722 by = y1; 1723 bh = y2 - y1; 1724 } else { 1725 by = y2; 1726 bh = y1 - y2; 1727 } 1728 1729 // RT-27378 1730 // TODO: casting down to floats everywhere here; evaluate later 1731 // to see if this is enough precision... 1732 // TODO: stroke normalization control? 1733 if (stroke.getType() == BasicStroke.TYPE_INNER) { 1734 return; 1735 } 1736 if (isComplexPaint) { 1737 scratchLine.setLine(x1, y1, x2, y2); 1738 renderWithComplexPaint(scratchLine, stroke, bx, by, bw, bh); 1739 return; 1740 } 1741 int cap = stroke.getEndCap(); 1742 if (stroke.isDashed()) { 1743 // NOTE: we could construct the GeneralPath directly 1744 // for CAP_ROUND and save a lot of processing in that case... 1745 // And again, we would need to deal with dropout control... 1746 scratchLine.setLine(x1, y1, x2, y2); 1747 renderShape(scratchLine, stroke, bx, by, bw, bh); 1748 return; 1749 } 1750 float lw = stroke.getLineWidth(); 1751 if (PrismSettings.primTextureSize != 0 && 1752 cap != BasicStroke.CAP_ROUND) 1753 { 1754 float pad = lw; 1755 if (stroke.getType() == BasicStroke.TYPE_CENTERED) { 1756 pad *= 0.5f; 1757 } 1758 if (bw == 0.0f || bh == 0.0f) { 1759 float padx, pady; 1760 if (cap == BasicStroke.CAP_SQUARE) { 1761 // CAP_SQUARE pads the same on all sides 1762 padx = pady = pad; 1763 // System.out.print("S"); System.out.flush(); 1764 } else if (bw != 0.0f) { 1765 // Horizontal CAP_BUTT line - widen vertically 1766 padx = 0.0f; 1767 pady = pad; 1768 // System.out.print("H"); System.out.flush(); 1769 } else if (bh != 0.0f) { 1770 // Vertical CAP_BUTT line - widen horizontally 1771 padx = pad; 1772 pady = 0.0f; 1773 // System.out.print("V"); System.out.flush(); 1774 } else { 1775 // System.out.print("0"); System.out.flush(); 1776 // Zero length line - NOP for CAP_BUTT 1777 return; 1778 } 1779 Texture rTex = context.getRectTexture(); 1780 Texture wTex = context.getWrapRectTexture(); 1781 boolean success = fillPrimRect(bx - padx, by - pady, 1782 bw + padx + padx, bh + pady + pady, 1783 rTex, wTex, bx, by, bw, bh); 1784 rTex.unlock(); 1785 wTex.unlock(); 1786 if (success) return; 1787 } else { 1788 if (drawPrimDiagonal(x1, y1, x2, y2, lw, cap, 1789 bx, by, bw, bh)) 1790 { 1791 return; 1792 } 1793 } 1794 } 1795 // System.out.print("#"); System.out.flush(); 1796 if (stroke.getType() == BasicStroke.TYPE_OUTER) { 1797 lw *= 2f; 1798 } 1799 float dx = x2 - x1; 1800 float dy = y2 - y1; 1801 float len = len(dx, dy); 1802 float ldx, ldy; // lw length vector in direction of line in user space 1803 if (len == 0) { 1804 if (cap == BasicStroke.CAP_BUTT) { 1805 return; 1806 } 1807 ldx = lw; 1808 ldy = 0; 1809 } else { 1810 ldx = lw * dx / len; 1811 ldy = lw * dy / len; 1812 } 1813 // The following is safe; this method does not mutate the transform 1814 BaseTransform xform = getTransformNoClone(); 1815 BaseTransform rendertx; 1816 float pdx, pdy; // ldx,ldy rotated 90 in user space then transformed 1817 if (isSimpleTranslate) { 1818 double tx = xform.getMxt(); 1819 double ty = xform.getMyt(); 1820 x1 += tx; 1821 y1 += ty; 1822 x2 += tx; 1823 y2 += ty; 1824 pdx = ldy; 1825 pdy = -ldx; 1826 rendertx = IDENT; 1827 } else { 1828 rendertx = extract3Dremainder(xform); 1829 double coords[] = {x1, y1, x2, y2}; 1830 xform.transform(coords, 0, coords, 0, 2); 1831 x1 = (float)coords[0]; 1832 y1 = (float)coords[1]; 1833 x2 = (float)coords[2]; 1834 y2 = (float)coords[3]; 1835 dx = x2 - x1; 1836 dy = y2 - y1; 1837 coords[0] = ldx; 1838 coords[1] = ldy; 1839 coords[2] = ldy; 1840 coords[3] = -ldx; 1841 xform.deltaTransform(coords, 0, coords, 0, 2); 1842 ldx = (float) coords[0]; 1843 ldy = (float) coords[1]; 1844 pdx = (float) coords[2]; 1845 pdy = (float) coords[3]; 1846 } 1847 float px = x1 - pdx / 2f; 1848 float py = y1 - pdy / 2f; 1849 float arcfractw, arcfracth; 1850 MaskType type; 1851 if (cap != BasicStroke.CAP_BUTT) { 1852 px -= ldx / 2f; 1853 py -= ldy / 2f; 1854 dx += ldx; 1855 dy += ldy; 1856 if (cap == BasicStroke.CAP_ROUND) { 1857 arcfractw = len(ldx, ldy) / len(dx, dy); 1858 arcfracth = 1f; 1859 type = MaskType.FILL_ROUNDRECT; 1860 } else { 1861 arcfractw = arcfracth = 0f; 1862 type = MaskType.FILL_PGRAM; 1863 } 1864 } else { 1865 arcfractw = arcfracth = 0f; 1866 type = MaskType.FILL_PGRAM; 1867 } 1868 renderGeneralRoundedPgram(px, py, dx, dy, pdx, pdy, 1869 arcfractw, arcfracth, 0f, 0f, 1870 rendertx, type, 1871 bx, by, bw, bh); 1872 } 1873 1874 private static float len(float x, float y) { 1875 return ((x == 0f) ? Math.abs(y) 1876 : ((y == 0f) ? Math.abs(x) 1877 : (float)Math.sqrt(x * x + y * y))); 1878 } 1879 1880 private boolean lcdSampleInvalid = false; 1881 1882 public void setNodeBounds(RectBounds bounds) { 1883 nodeBounds = bounds; 1884 lcdSampleInvalid = bounds != null; 1885 } 1886 1887 private void initLCDSampleRT() { 1888 if (lcdSampleInvalid) { 1889 RectBounds textBounds = new RectBounds(); 1890 getTransformNoClone().transform(nodeBounds, textBounds); 1891 Rectangle clipRect = getClipRectNoClone(); 1892 if (clipRect != null && !clipRect.isEmpty()) { 1893 // Reduce sample area if there is any clipping bounds set 1894 textBounds.intersectWith(clipRect); 1895 } 1896 // LCD mixing with background will often lead to extra pixel at edge 1897 // thus adding a single pixel, as padding, at edges and bottom. 1898 float bx = textBounds.getMinX() - 1.0f; 1899 float by = textBounds.getMinY(); 1900 float bw = textBounds.getWidth() + 2.0f; 1901 float bh = textBounds.getHeight() + 1.0f; 1902 1903 context.validateLCDBuffer(getRenderTarget()); 1904 1905 // Create a graphics for us to render into the LCDBuffer 1906 // Note this also sets the current RenderTarget as the LCDBuffer 1907 BaseShaderGraphics bsg = (BaseShaderGraphics) context.getLCDBuffer().createGraphics(); 1908 bsg.setCompositeMode(CompositeMode.SRC); 1909 context.validateLCDOp(bsg, IDENT, (Texture) getRenderTarget(), null, true, null); 1910 1911 int srch = getRenderTarget().getPhysicalHeight(); 1912 int srcw = getRenderTarget().getPhysicalWidth(); 1913 float tx1 = bx / srcw; 1914 float ty1 = by / srch; 1915 float tx2 = (bx + bw) / srcw; 1916 float ty2 = (by + bh) / srch; 1917 1918 //sample the source RT in the following bounds and store it in the LCDBuffer RT. 1919 bsg.drawLCDBuffer(bx, by, bw, bh, tx1, ty1, tx2, ty2); 1920 context.setRenderTarget(this); 1921 } 1922 lcdSampleInvalid = false; 1923 } 1924 1925 public void drawString(GlyphList gl, FontStrike strike, float x, float y, 1926 Color selectColor, int selectStart, int selectEnd) { 1927 1928 if (isComplexPaint || 1929 paint.getType().isImagePattern() || 1930 strike.drawAsShapes()) 1931 { 1932 // FontStrike.drawAsShapes() may be true for very large font sizes 1933 // in which case so no glyph images are cached. 1934 // The Prism Text node handles such cases directly, but Webnode relies 1935 // upon Prism's drawString method, so we need to handle it here too. 1936 1937 // this is not a very optimal approach and may not hit exactly 1938 // the same pixels that we would hit in the case where the 1939 // glyph cache is used, but the complex paint case is not 1940 // common enough to warrant further optimization 1941 BaseTransform xform = BaseTransform.getTranslateInstance(x, y); 1942 Shape shape = strike.getOutline(gl, xform); 1943 fill(shape); 1944 return; 1945 } 1946 1947 BaseTransform xform = getTransformNoClone(); 1948 1949 Paint textPaint = getPaint(); 1950 Color textColor = textPaint.getType() == Paint.Type.COLOR ? 1951 (Color) textPaint : null; 1952 1953 CompositeMode blendMode = getCompositeMode(); 1954 // LCD support requires several attributes to function: 1955 // FontStrike supports LCD, SRC_OVER CompositeMode and Paint is a COLOR 1956 boolean lcdSupported = blendMode == CompositeMode.SRC_OVER && 1957 textColor != null && 1958 xform.is2D() && 1959 !getRenderTarget().isAntiAliasing(); 1960 1961 /* If the surface can't support LCD text we need to replace an 1962 * LCD mode strike with the equivalent grey scale one. 1963 */ 1964 if (strike.getAAMode() == FontResource.AA_LCD && !lcdSupported) { 1965 FontResource fr = strike.getFontResource(); 1966 float size = strike.getSize(); 1967 BaseTransform tx = strike.getTransform(); 1968 strike = fr.getStrike(size, tx, FontResource.AA_GREYSCALE); 1969 } 1970 1971 float bx = 0f, by = 0f, bw = 0f, bh = 0f; 1972 if (paint.getType().isGradient() && ((Gradient)paint).isProportional()) { 1973 // If drawString is called directly without using setNodeBounds, 1974 // then nodeBounds is null, and we must determine the bounds based 1975 // on the str(vs. the node). 1976 RectBounds textBounds = nodeBounds; 1977 if (textBounds == null) { 1978 Metrics m = strike.getMetrics(); 1979 float pad = -m.getAscent() * 0.4f; 1980 textBounds = new RectBounds(-pad, 1981 m.getAscent(), 1982 gl.getWidth() + 2.0f *pad, 1983 m.getDescent() + m.getLineGap()); 1984 bx = x; 1985 by = y; 1986 } 1987 1988 bx += textBounds.getMinX(); 1989 by += textBounds.getMinY(); 1990 bw = textBounds.getWidth(); 1991 bh = textBounds.getHeight(); 1992 } 1993 1994 BaseBounds clip = null; 1995 Point2D p2d = new Point2D(x, y); 1996 if (isSimpleTranslate) { 1997 /* Only use clip for simple transforms so that coordinates in the 1998 * glyph list (user space) and the coordinates of the clip 1999 * (device space) can be intersected. 2000 */ 2001 clip = getFinalClipNoClone(); 2002 xform = IDENT; 2003 p2d.x += transX; 2004 p2d.y += transY; 2005 } 2006 2007 /* Cache look up needs to be on the font as rendered, including 2008 * AA mode, metrics mode, glyph transform (pt size combined with 2009 * the full graphics transform). Most of this info is expected to 2010 * be in the font, which here is close to being a full strike 2011 * description. 2012 */ 2013 GlyphCache glyphCache = context.getGlyphCache(strike); 2014 Texture cacheTex = glyphCache.getBackingStore(); 2015 2016 //Since we currently cannot support LCD text on transparant surfaces, we 2017 //verify that we are drawing to an opaque surface. 2018 if (strike.getAAMode() == FontResource.AA_LCD) { 2019 if (nodeBounds == null) { 2020 // If drawString is called directly without using 2021 // setNodeBounds then we must determine the bounds of the str, 2022 // before we render background to texture. 2023 // This is slow, but required by webnode. 2024 2025 Metrics m = strike.getMetrics(); 2026 // Ruff guess for padding, since lots of glyphs exceed advance 2027 RectBounds textBounds = 2028 new RectBounds(x - 2, 2029 y + m.getAscent(), 2030 x + 2 + gl.getWidth(), 2031 y + 1 + m.getDescent() + m.getLineGap()); 2032 2033 setNodeBounds(textBounds); 2034 initLCDSampleRT(); 2035 setNodeBounds(null); 2036 } else { 2037 initLCDSampleRT(); 2038 } 2039 float invgamma = PrismFontFactory.getLCDContrast(); 2040 float gamma = 1.0f/invgamma; 2041 textColor = new Color((float)Math.pow(textColor.getRed(), invgamma), 2042 (float)Math.pow(textColor.getGreen(), invgamma), 2043 (float)Math.pow(textColor.getBlue(), invgamma), 2044 (float)Math.pow(textColor.getAlpha(), invgamma)); 2045 if (selectColor != null) { 2046 selectColor = new Color( 2047 (float)Math.pow(selectColor.getRed(), invgamma), 2048 (float)Math.pow(selectColor.getGreen(), invgamma), 2049 (float)Math.pow(selectColor.getBlue(), invgamma), 2050 (float)Math.pow(selectColor.getAlpha(), invgamma)); 2051 } 2052 2053 // In order to handle transparency, the LCD shader need to manually 2054 // composite source with destination. Thus, SRC_OVER compositing 2055 // needs to be set to SRC, while shader is active. 2056 setCompositeMode(CompositeMode.SRC); 2057 2058 //set our 2nd LCD shader. 2059 Shader shader = context.validateLCDOp(this, IDENT, 2060 context.getLCDBuffer(), 2061 cacheTex, false, textColor); 2062 2063 float unitXCoord = 1.0f/((float)cacheTex.getPhysicalWidth()); 2064 shader.setConstant("gamma", gamma, invgamma, unitXCoord); 2065 setCompositeMode(blendMode); // Restore composite mode 2066 } else { 2067 context.validatePaintOp(this, IDENT, cacheTex, bx, by, bw, bh); 2068 } 2069 if (isSimpleTranslate) { 2070 // Applying this rounding allows for smoother text animation, 2071 // when animating simple translated text. 2072 // Asking glyph textures to be rendered at non-integral 2073 // locations produces very poor text. This doesn't solve 2074 // the problem for scaled (etc) cases, but addresses a 2075 // common case. 2076 p2d.y = Math.round(p2d.y); 2077 p2d.x = Math.round(p2d.x); 2078 } 2079 glyphCache.render(context, gl, p2d.x, p2d.y, selectStart, selectEnd, 2080 selectColor, textColor, xform, clip); 2081 } 2082 2083 //This function is used by the LCD path to render a quad into the 2084 //LCD RTT Texture. here the presentable is set as input and 2085 //sampled using texture coordinates. This is later used in a 2086 //second pass to provide the dst color as input. 2087 private void drawLCDBuffer(float bx, float by, float bw, float bh, 2088 float tx1, float ty1, float tx2, float ty2) 2089 { 2090 context.setRenderTarget(this); 2091 context.getVertexBuffer().addQuad(bx, by, bx + bw, by + bh, tx1, ty1, tx2, ty2); 2092 } 2093 2094 public boolean canReadBack() { 2095 RenderTarget rt = getRenderTarget(); 2096 return rt instanceof ReadbackRenderTarget && 2097 ((ReadbackRenderTarget) rt).getBackBuffer() != null; 2098 } 2099 2100 public RTTexture readBack(Rectangle view) { 2101 RenderTarget rt = getRenderTarget(); 2102 context.flushVertexBuffer(); 2103 context.validateLCDBuffer(rt); 2104 RTTexture lcdrtt = context.getLCDBuffer(); 2105 Texture bbtex = ((ReadbackRenderTarget) rt).getBackBuffer(); 2106 2107 float x1 = view.x; 2108 float y1 = view.y; 2109 float x2 = x1 + view.width; 2110 float y2 = y1 + view.height; 2111 2112 // Create a graphics for us to render into the LCDBuffer 2113 // Note this also sets the current RenderTarget as the LCDBuffer 2114 BaseShaderGraphics bsg = (BaseShaderGraphics) lcdrtt.createGraphics(); 2115 bsg.setCompositeMode(CompositeMode.SRC); 2116 context.validateTextureOp(bsg, IDENT, bbtex, bbtex.getPixelFormat()); 2117 2118 // sample the source RT in the following bounds and store it in the LCDBuffer RT. 2119 bsg.drawTexture(bbtex, 0, 0, view.width, view.height, x1, y1, x2, y2); 2120 context.flushVertexBuffer(); 2121 2122 // set the RenderTarget back to this. 2123 context.setRenderTarget(this); 2124 return lcdrtt; 2125 } 2126 2127 public void releaseReadBackBuffer(RTTexture rtt) { 2128 // This will be needed when we track LCD buffer locks and uses. 2129 // (See RT-29488) 2130 // context.releaseLCDBuffer(); 2131 } 2132 2133 public void setup3DRendering() { 2134 context.setRenderTarget(this); 2135 } 2136 }