1 /* 2 * Copyright (c) 2012, 2015, 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 javafx.scene.layout; 27 28 import javafx.beans.NamedArg; 29 import javafx.css.CssMetaData; 30 import javafx.css.Styleable; 31 import javafx.geometry.Insets; 32 import javafx.scene.Node; 33 import javafx.scene.image.Image; 34 import javafx.scene.paint.Color; 35 import javafx.scene.paint.Paint; 36 import java.util.Arrays; 37 import java.util.Collections; 38 import java.util.List; 39 import com.sun.javafx.UnmodifiableArrayList; 40 import com.sun.javafx.css.SubCssMetaData; 41 import com.sun.javafx.css.converters.InsetsConverter; 42 import com.sun.javafx.css.converters.PaintConverter; 43 import com.sun.javafx.css.converters.URLConverter; 44 import com.sun.javafx.scene.layout.region.LayeredBackgroundPositionConverter; 45 import com.sun.javafx.scene.layout.region.LayeredBackgroundSizeConverter; 46 import com.sun.javafx.scene.layout.region.CornerRadiiConverter; 47 import com.sun.javafx.scene.layout.region.RepeatStruct; 48 import com.sun.javafx.scene.layout.region.RepeatStructConverter; 49 import com.sun.javafx.tk.Toolkit; 50 51 /** 52 * The Background of a {@link Region}. A Background is an immutable object which 53 * encapsulates the entire set of data required to render the background 54 * of a Region. Because this class is immutable, you can freely reuse the same 55 * Background on many different Regions. Please refer to 56 * {@link ../doc-files/cssref.html JavaFX CSS Reference} for a complete description 57 * of the CSS rules for styling the background of a Region. 58 * <p/> 59 * Every Background is comprised of {@link #getFills() fills} and / or 60 * {@link #getImages() images}. Neither list will ever be null, but either or 61 * both may be empty. Each defined {@link BackgroundFill} is rendered in order, 62 * followed by each defined {@link BackgroundImage}. 63 * <p/> 64 * The Background's {@link #getOutsets() outsets} define any extension of the drawing area of a Region 65 * which is necessary to account for all background drawing. These outsets are strictly 66 * defined by the BackgroundFills that are specified on this Background, if any, because 67 * all BackgroundImages are clipped to the drawing area, and do not define it. The 68 * outsets values are strictly non-negative. 69 * 70 * @since JavaFX 8.0 71 */ 72 @SuppressWarnings("unchecked") 73 public final class Background { 74 static final CssMetaData<Node,Paint[]> BACKGROUND_COLOR = 75 new SubCssMetaData<>("-fx-background-color", 76 PaintConverter.SequenceConverter.getInstance(), 77 new Paint[] {Color.TRANSPARENT}); 78 79 static final CssMetaData<Node,CornerRadii[]> BACKGROUND_RADIUS = 80 new SubCssMetaData<>("-fx-background-radius", 81 CornerRadiiConverter.getInstance(), 82 new CornerRadii[] {CornerRadii.EMPTY}); 83 84 static final CssMetaData<Node,Insets[]> BACKGROUND_INSETS = 85 new SubCssMetaData<>("-fx-background-insets", 86 InsetsConverter.SequenceConverter.getInstance(), 87 new Insets[] {Insets.EMPTY}); 88 89 static final CssMetaData<Node,Image[]> BACKGROUND_IMAGE = 90 new SubCssMetaData<>("-fx-background-image", 91 URLConverter.SequenceConverter.getInstance()); 92 93 static final CssMetaData<Node,RepeatStruct[]> BACKGROUND_REPEAT = 94 new SubCssMetaData<>("-fx-background-repeat", 95 RepeatStructConverter.getInstance(), 96 new RepeatStruct[] {new RepeatStruct(BackgroundRepeat.REPEAT, 97 BackgroundRepeat.REPEAT) }); 98 99 static final CssMetaData<Node,BackgroundPosition[]> BACKGROUND_POSITION = 100 new SubCssMetaData<>("-fx-background-position", 101 LayeredBackgroundPositionConverter.getInstance(), 102 new BackgroundPosition[] { BackgroundPosition.DEFAULT }); 103 104 static final CssMetaData<Node,BackgroundSize[]> BACKGROUND_SIZE = 105 new SubCssMetaData<>("-fx-background-size", 106 LayeredBackgroundSizeConverter.getInstance(), 107 new BackgroundSize[] { BackgroundSize.DEFAULT } ); 108 109 private static final List<CssMetaData<? extends Styleable, ?>> STYLEABLES = 110 (List<CssMetaData<? extends Styleable, ?>>) (List) Collections.unmodifiableList( 111 // Unchecked! 112 Arrays.asList(BACKGROUND_COLOR, 113 BACKGROUND_INSETS, 114 BACKGROUND_RADIUS, 115 BACKGROUND_IMAGE, 116 BACKGROUND_REPEAT, 117 BACKGROUND_POSITION, 118 BACKGROUND_SIZE)); 119 120 /** 121 * @return The CssMetaData associated with this class, which may include the 122 * CssMetaData of its super classes. 123 */ 124 public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() { 125 return STYLEABLES; 126 } 127 128 /** 129 * An empty Background, useful to use instead of null. 130 */ 131 public static final Background EMPTY = new Background((BackgroundFill[])null, null); 132 133 /** 134 * The list of BackgroundFills which together define the filled portion 135 * of this Background. This List is unmodifiable and immutable. It 136 * will never be null. The elements of this list will also never be null. 137 */ 138 public final List<BackgroundFill> getFills() { return fills; } 139 final List<BackgroundFill> fills; 140 141 /** 142 * The list of BackgroundImages which together define the image portion 143 * of this Background. This List is unmodifiable and immutable. It 144 * will never be null. The elements of this list will also never be null. 145 */ 146 public final List<BackgroundImage> getImages() { return images; } 147 final List<BackgroundImage> images; 148 149 /** 150 * The outsets of this Background. This represents the largest 151 * bounding rectangle within which all drawing for the Background 152 * will take place. The outsets will never be negative, and represent 153 * the distance from the edge of the Region outward. Any BackgroundImages 154 * which would extend beyond the outsets will be clipped. Only the 155 * BackgroundFills contribute to the outsets. 156 */ 157 public final Insets getOutsets() { return outsets; } 158 final Insets outsets; 159 160 /** 161 * Gets whether the background is empty. It is empty if there are no fills or images. 162 * @return true if the Background is empty, false otherwise. 163 */ 164 public final boolean isEmpty() { 165 return fills.isEmpty() && images.isEmpty(); 166 } 167 168 /** 169 * Specifies whether the Background has at least one opaque fill. 170 */ 171 private final boolean hasOpaqueFill; 172 173 /** 174 * Package-private immutable fields referring to the opaque insets 175 * of this Background. 176 */ 177 private final double opaqueFillTop, opaqueFillRight, opaqueFillBottom, opaqueFillLeft; 178 final boolean hasPercentageBasedOpaqueFills; 179 180 /** 181 * True if there are any fills that are in some way based on the size of the region. 182 * For example, if a CornerRadii on the fill is percentage based in either or both 183 * dimensions. 184 */ 185 final boolean hasPercentageBasedFills; 186 187 /** 188 * The cached hash code computation for the Background. One very big 189 * reason for making Background immutable was to make it possible to 190 * cache and reuse the same Background instance for multiple 191 * Regions (for example, every un-hovered Button should have the same 192 * Background instance). To enable efficient caching, we cache the hash. 193 */ 194 private final int hash; 195 196 /** 197 * Create a new Background by supplying an array of BackgroundFills. 198 * This array may be null, or may contain null values. Any null values 199 * will be ignored and will not contribute to the {@link #getFills() fills} 200 * or {@link #getOutsets() outsets}. 201 * 202 * @param fills The fills. This may be null, and may contain nulls. Any 203 * contained nulls are filtered out and not included in the 204 * final List of fills. A null array becomes an empty List. 205 */ 206 public Background(final @NamedArg("fills") BackgroundFill... fills) { 207 this(fills, null); 208 } 209 210 /** 211 * Create a new Background by supplying an array of BackgroundImages. 212 * This array may be null, or may contain null values. Any null values will 213 * be ignored and will not contribute to the {@link #getImages() images}. 214 * 215 * @param images The images. This may be null, and may contain nulls. Any 216 * contained nulls are filtered out and not included in the 217 * final List of images. A null array becomes an empty List. 218 */ 219 public Background(final @NamedArg("images") BackgroundImage... images) { 220 this(null, images); 221 } 222 223 /** 224 * Create a new Background supply two Lists, one for background fills and 225 * one for background images. Either list may be null, and may contain nulls. 226 * Any null values in these lists will be ignored and will not 227 * contribute to the {@link #getFills() fills}, {@link #getImages() images}, or 228 * {@link #getOutsets() outsets}. 229 * 230 * @param fills The fills. This may be null, and may contain nulls. Any 231 * contained nulls are filtered out and not included in the 232 * final List of fills. A null List becomes an empty List. 233 * @param images The images. This may be null, and may contain nulls. Any 234 * contained nulls are filtered out and not included in the 235 * final List of images. A null List becomes an empty List. 236 */ 237 public Background(final @NamedArg("fills") List<BackgroundFill> fills, final @NamedArg("images") List<BackgroundImage> images) { 238 // NOTE: This constructor had to be supplied in order to cause a Builder 239 // to be auto-generated, because otherwise the types of the fills and images 240 // properties didn't match the types of the array based constructor parameters. 241 // So a Builder will use this constructor, while the CSS engine uses the 242 // array based constructor (for speed). 243 this(fills == null ? null : fills.toArray(new BackgroundFill[fills.size()]), 244 images == null ? null : images.toArray(new BackgroundImage[images.size()])); 245 } 246 247 /** 248 * Create a new Background by supplying two arrays, one for background fills, 249 * and one for background images. Either array may be null, and may contain null 250 * values. Any null values in these arrays will be ignored and will not 251 * contribute to the {@link #getFills() fills}, {@link #getImages() images}, or 252 * {@link #getOutsets() outsets}. 253 * 254 * @param fills The fills. This may be null, and may contain nulls. Any 255 * contained nulls are filtered out and not included in the 256 * final List of fills. A null array becomes an empty List. 257 * @param images The images. This may be null, and may contain nulls. Any 258 * contained nulls are filtered out and not included in the 259 * final List of images. A null array becomes an empty List. 260 */ 261 public Background(final @NamedArg("fills") BackgroundFill[] fills, final @NamedArg("images") BackgroundImage[] images) { 262 // The cumulative insets 263 double outerTop = 0, outerRight = 0, outerBottom = 0, outerLeft = 0; 264 boolean hasPercentOpaqueInsets = false; 265 boolean hasPercentFillRadii = false; 266 boolean opaqueFill = false; 267 268 // If the fills is empty or null then we know we can just use the shared 269 // immutable empty list from Collections. 270 if (fills == null || fills.length == 0) { 271 this.fills = Collections.emptyList(); 272 } else { 273 // We need to iterate over all of the supplied elements in the fills array. 274 // Each null element is ignored. Each non-null element is inspected to 275 // see if it contributes to the outsets. 276 final BackgroundFill[] noNulls = new BackgroundFill[fills.length]; 277 int size = 0; 278 for (int i=0; i<fills.length; i++) { 279 final BackgroundFill fill = fills[i]; 280 if (fill != null) { 281 noNulls[size++] = fill; 282 final Insets fillInsets = fill.getInsets(); 283 final double fillTop = fillInsets.getTop(); 284 final double fillRight = fillInsets.getRight(); 285 final double fillBottom = fillInsets.getBottom(); 286 final double fillLeft = fillInsets.getLeft(); 287 outerTop = outerTop <= fillTop ? outerTop : fillTop; // min 288 outerRight = outerRight <= fillRight ? outerRight : fillRight; // min 289 outerBottom = outerBottom <= fillBottom ? outerBottom : fillBottom; // min 290 outerLeft = outerLeft <= fillLeft ? outerLeft : fillLeft; // min 291 292 // The common case is to NOT have percent based radii 293 final boolean b = fill.getRadii().hasPercentBasedRadii; 294 hasPercentFillRadii |= b; 295 if (fill.fill.isOpaque()) { 296 opaqueFill = true; 297 if (b) { 298 hasPercentOpaqueInsets = true; 299 } 300 } 301 } 302 } 303 this.fills = new UnmodifiableArrayList<>(noNulls, size); 304 } 305 hasPercentageBasedFills = hasPercentFillRadii; 306 307 // This ensures that we either have outsets of 0, if all the insets were positive, 308 // or a value greater than zero if they were negative. 309 outsets = new Insets( 310 Math.max(0, -outerTop), 311 Math.max(0, -outerRight), 312 Math.max(0, -outerBottom), 313 Math.max(0, -outerLeft)); 314 315 // An null or empty images array results in an empty list 316 if (images == null || images.length == 0) { 317 this.images = Collections.emptyList(); 318 } else { 319 // Filter out any null values and create an immutable array list 320 final BackgroundImage[] noNulls = new BackgroundImage[images.length]; 321 int size = 0; 322 for (int i=0; i<images.length; i++) { 323 final BackgroundImage image = images[i]; 324 if (image != null) noNulls[size++] = image; 325 } 326 this.images = new UnmodifiableArrayList<>(noNulls, size); 327 } 328 329 hasOpaqueFill = opaqueFill; 330 if (hasPercentOpaqueInsets) { 331 opaqueFillTop = Double.NaN; 332 opaqueFillRight = Double.NaN; 333 opaqueFillBottom = Double.NaN; 334 opaqueFillLeft = Double.NaN; 335 } else { 336 double[] trbl = new double[4]; 337 computeOpaqueInsets(1, 1, true, trbl); 338 opaqueFillTop = trbl[0]; 339 opaqueFillRight = trbl[1]; 340 opaqueFillBottom = trbl[2]; 341 opaqueFillLeft = trbl[3]; 342 } 343 hasPercentageBasedOpaqueFills = hasPercentOpaqueInsets; 344 345 // Pre-compute the hash code. NOTE: all variables are prefixed with "this" so that we 346 // do not accidentally compute the hash based on the constructor arguments rather than 347 // based on the fields themselves! 348 int result = this.fills.hashCode(); 349 result = 31 * result + this.images.hashCode(); 350 hash = result; 351 } 352 353 /** 354 * Gets whether the fill of this Background is based on percentages (that is, relative to the 355 * size of the region being styled). Specifically, this returns true if any of the CornerRadii 356 * on any of the fills on this Background has a radius that is based on percentages. 357 * 358 * @return True if any CornerRadii of any BackgroundFill on this background would return true, false otherwise. 359 * @since JavaFX 8.0 360 */ 361 public boolean isFillPercentageBased() { 362 return hasPercentageBasedFills; 363 } 364 365 /** 366 * Computes the opaque insets for a region with the specified width and height. This call 367 * must be made whenever the width or height of the region change, because the opaque insets 368 * are based on background fills, and the corner radii of a background fill can be percentage 369 * based. Thus, we need to potentially recompute the opaque insets whenever the width or 370 * height of the region change. On the other hand, if there are no percentage based corner 371 * radii, then we can simply return the pre-computed and cached answers. 372 * 373 * @param width The width of the region 374 * @param height The height of the region 375 * @param trbl A four-element array of doubles in order: top, right, bottom, left. 376 */ 377 void computeOpaqueInsets(double width, double height, double[] trbl) { 378 computeOpaqueInsets(width, height, false, trbl); 379 } 380 381 /** 382 * Computes the opaque insets. The first time this is called from the constructor 383 * we want to take the long route through and compute everything, whether there are 384 * percentage based insets or not (the constructor ensures not to call it in the case 385 * that it has percentage based insets!). All other times, this is called by the other 386 * computeOpaqueInsets method with "firstTime" set to false, such that if we have 387 * percentage based insets, then we will bail early. 388 * 389 * This method takes into account both fills and images. Because images can be 390 * lazy loaded, we cannot pre-compute a bunch of things in the constructor for images 391 * the way we can with fills. Instead, each time the method is called, we have to 392 * inspect the images. However, we do have fast paths for cases where fills are used 393 * and not images. 394 * 395 * @param width The width of the region 396 * @param height The height of the region 397 * @param firstTime Whether this is being called from the constructor 398 * @param trbl A four-element array of doubles in order: top, right, bottom, left. 399 */ 400 private void computeOpaqueInsets(double width, double height, boolean firstTime, double[] trbl) { 401 double opaqueRegionTop = Double.NaN, 402 opaqueRegionRight = Double.NaN, 403 opaqueRegionBottom = Double.NaN, 404 opaqueRegionLeft = Double.NaN; 405 406 // If during object construction we determined that there is an opaque fill, then we need 407 // to visit the fills and figure out which ones contribute to the opaque insets 408 if (hasOpaqueFill) { 409 // If during construction time we determined that none of the fills had a percentage based 410 // opaque inset, then we can just use the pre-computed values. This is worth doing since 411 // at this time all CSS based radii for BackgroundFills are literal values! 412 if (!firstTime && !hasPercentageBasedOpaqueFills) { 413 opaqueRegionTop = opaqueFillTop; 414 opaqueRegionRight = opaqueFillRight; 415 opaqueRegionBottom = opaqueFillBottom; 416 opaqueRegionLeft = opaqueFillLeft; 417 } else { 418 // NOTE: We know at this point that there is an opaque fill, and that at least one 419 // of them uses a percentage for at least one corner radius. Iterate over each 420 // BackgroundFill. If the fill is opaque, then we will compute the largest rectangle 421 // which will fit within its opaque area, taking the corner radii into account. 422 // Initialize them to the "I Don't Know" answer. 423 424 for (int i=0, max=fills.size(); i<max; i++) { 425 final BackgroundFill fill = fills.get(i); 426 final Insets fillInsets = fill.getInsets(); 427 final double fillTop = fillInsets.getTop(); 428 final double fillRight = fillInsets.getRight(); 429 final double fillBottom = fillInsets.getBottom(); 430 final double fillLeft = fillInsets.getLeft(); 431 432 if (fill.fill.isOpaque()) { 433 // Some possible configurations: 434 // (a) rect1 is completely contained by rect2 435 // (b) rect2 is completely contained by rect1 436 // (c) rect1 is the same height as rect 2 and they overlap on the left or right 437 // (d) rect1 is the same width as rect 2 and they overlap on the top or bottom 438 // (e) they are disjoint or overlap in an unsupported manner. 439 final CornerRadii radii = fill.getRadii(); 440 final double topLeftHorizontalRadius = radii.isTopLeftHorizontalRadiusAsPercentage() ? 441 width * radii.getTopLeftHorizontalRadius() : radii.getTopLeftHorizontalRadius(); 442 final double topLeftVerticalRadius = radii.isTopLeftVerticalRadiusAsPercentage() ? 443 height * radii.getTopLeftVerticalRadius() : radii.getTopLeftVerticalRadius(); 444 final double topRightVerticalRadius = radii.isTopRightVerticalRadiusAsPercentage() ? 445 height * radii.getTopRightVerticalRadius() : radii.getTopRightVerticalRadius(); 446 final double topRightHorizontalRadius = radii.isTopRightHorizontalRadiusAsPercentage() ? 447 width * radii.getTopRightHorizontalRadius() : radii.getTopRightHorizontalRadius(); 448 final double bottomRightHorizontalRadius = radii.isBottomRightHorizontalRadiusAsPercentage() ? 449 width * radii.getBottomRightHorizontalRadius() : radii.getBottomRightHorizontalRadius(); 450 final double bottomRightVerticalRadius = radii.isBottomRightVerticalRadiusAsPercentage() ? 451 height * radii.getBottomRightVerticalRadius() : radii.getBottomRightVerticalRadius(); 452 final double bottomLeftVerticalRadius = radii.isBottomLeftVerticalRadiusAsPercentage() ? 453 height * radii.getBottomLeftVerticalRadius() : radii.getBottomLeftVerticalRadius(); 454 final double bottomLeftHorizontalRadius = radii.isBottomLeftHorizontalRadiusAsPercentage() ? 455 width * radii.getBottomLeftHorizontalRadius() : radii.getBottomLeftHorizontalRadius(); 456 457 final double t = fillTop + (Math.max(topLeftVerticalRadius, topRightVerticalRadius) / 2); 458 final double r = fillRight + (Math.max(topRightHorizontalRadius, bottomRightHorizontalRadius) / 2); 459 final double b = fillBottom + (Math.max(bottomLeftVerticalRadius, bottomRightVerticalRadius) / 2); 460 final double l = fillLeft + (Math.max(topLeftHorizontalRadius, bottomLeftHorizontalRadius) / 2); 461 if (Double.isNaN(opaqueRegionTop)) { 462 // This only happens for the first opaque fill we encounter 463 opaqueRegionTop = t; 464 opaqueRegionRight = r; 465 opaqueRegionBottom = b; 466 opaqueRegionLeft = l; 467 } else { 468 final boolean largerTop = t >= opaqueRegionTop; 469 final boolean largerRight = r >= opaqueRegionRight; 470 final boolean largerBottom = b >= opaqueRegionBottom; 471 final boolean largerLeft = l >= opaqueRegionLeft; 472 if (largerTop && largerRight && largerBottom && largerLeft) { 473 // The new fill is completely contained within the existing rect, so no change 474 continue; 475 } else if (!largerTop && !largerRight && !largerBottom && !largerLeft) { 476 // The new fill completely contains the existing rect, so use these 477 // new values for our opaque region 478 opaqueRegionTop = fillTop; 479 opaqueRegionRight = fillRight; 480 opaqueRegionBottom = fillBottom; 481 opaqueRegionLeft = fillLeft; 482 } else if (l == opaqueRegionLeft && r == opaqueRegionRight) { 483 // The left and right insets are the same between the two rects, so just pick 484 // the smallest top and bottom 485 opaqueRegionTop = Math.min(t, opaqueRegionTop); 486 opaqueRegionBottom = Math.min(b, opaqueRegionBottom); 487 } else if (t == opaqueRegionTop && b == opaqueRegionBottom) { 488 // The top and bottom are the same between the two rects so just pick 489 // the smallest left and right 490 opaqueRegionLeft = Math.min(l, opaqueRegionLeft); 491 opaqueRegionRight = Math.min(r, opaqueRegionRight); 492 } else { 493 // They are disjoint or overlap in some other manner. So we will just 494 // ignore this region. 495 continue; 496 } 497 } 498 } 499 } 500 } 501 } 502 503 // Check the background images. Since the image of a BackgroundImage might load asynchronously 504 // and since we must inspect the image to check for opacity, we just have to visit all the 505 // images each time this method is called rather than pre-computing results. With some work 506 // we could end up caching the result eventually. 507 final Toolkit.ImageAccessor acc = Toolkit.getImageAccessor(); 508 for (BackgroundImage bi : images) { 509 if (bi.opaque == null) { 510 // If the image is not yet loaded, just skip it 511 // Note: Unit test wants this to be com.sun.javafx.tk.PlatformImage, not com.sun.prism.Image 512 final com.sun.javafx.tk.PlatformImage platformImage = acc.getImageProperty(bi.image).get(); 513 if (platformImage == null) continue; 514 515 // The image has been loaded, so update the opaque flag 516 if (platformImage instanceof com.sun.prism.Image) { 517 bi.opaque = ((com.sun.prism.Image)platformImage).isOpaque(); 518 } else { 519 continue; 520 } 521 } 522 523 // At this point we know that we're processing an image which has already been resolved 524 // and we know whether it is opaque or not. Of course, we only care about processing 525 // opaque images. 526 if (bi.opaque) { 527 if (bi.size.cover || 528 (bi.size.height == BackgroundSize.AUTO && bi.size.width == BackgroundSize.AUTO && 529 bi.size.widthAsPercentage && bi.size.heightAsPercentage)) { 530 // If the size mode is "cover" or AUTO, AUTO, and percentage based, then we're done -- we can simply 531 // accumulate insets of "0" 532 opaqueRegionTop = Double.isNaN(opaqueRegionTop) ? 0 : Math.min(0, opaqueRegionTop); 533 opaqueRegionRight = Double.isNaN(opaqueRegionRight) ? 0 : Math.min(0, opaqueRegionRight); 534 opaqueRegionBottom = Double.isNaN(opaqueRegionBottom) ? 0 : Math.min(0, opaqueRegionBottom); 535 opaqueRegionLeft = Double.isNaN(opaqueRegionLeft) ? 0 : Math.min(0, opaqueRegionLeft); 536 break; 537 } else { 538 // Here we are taking into account all potential tiling cases including "contain". Basically, 539 // as long as the repeat is *not* SPACE, we know that we'll be touching every pixel, and we 540 // don't really care how big the tiles end up being. The only case where we care about the 541 // actual tile size is in the NO_REPEAT modes. 542 543 // If the repeatX or repeatY includes "SPACE" Then we bail, because we can't be happy about 544 // spaces strewn about within the region. 545 if (bi.repeatX == BackgroundRepeat.SPACE || bi.repeatY == BackgroundRepeat.SPACE) { 546 bi.opaque = false; // We'll treat it as false in the future 547 continue; 548 } 549 550 // If the repeatX and repeatY are "REPEAT" and/or "ROUND" (any combination thereof) then 551 // we know all pixels within the region width / height are being touched, so we can just 552 // set the opaqueRegion variables and we're done. 553 final boolean filledX = bi.repeatX == BackgroundRepeat.REPEAT || bi.repeatX == BackgroundRepeat.ROUND; 554 final boolean filledY = bi.repeatY == BackgroundRepeat.REPEAT || bi.repeatY == BackgroundRepeat.ROUND; 555 if (filledX && filledY) { 556 opaqueRegionTop = Double.isNaN(opaqueRegionTop) ? 0 : Math.min(0, opaqueRegionTop); 557 opaqueRegionRight = Double.isNaN(opaqueRegionRight) ? 0 : Math.min(0, opaqueRegionRight); 558 opaqueRegionBottom = Double.isNaN(opaqueRegionBottom) ? 0 : Math.min(0, opaqueRegionBottom); 559 opaqueRegionLeft = Double.isNaN(opaqueRegionLeft) ? 0 : Math.min(0, opaqueRegionLeft); 560 break; 561 } 562 563 // We know that one or the other dimension is not filled, so we have to compute the right 564 // width / height. This is basically a big copy/paste from NGRegion! Blah! 565 final double w = bi.size.widthAsPercentage ? bi.size.width * width : bi.size.width; 566 final double h = bi.size.heightAsPercentage ? bi.size.height * height : bi.size.height; 567 final double imgUnscaledWidth = bi.image.getWidth(); 568 final double imgUnscaledHeight = bi.image.getHeight(); 569 570 // Now figure out the width and height of each tile to be drawn. The actual image 571 // dimensions may be one thing, but we need to figure out what the size of the image 572 // in the destination is going to be. 573 final double tileWidth, tileHeight; 574 if (bi.size.contain) { 575 // In the case of "contain", we compute the destination size based on the largest 576 // possible scale such that the aspect ratio is maintained, yet one side of the 577 // region is completely filled. 578 final double scaleX = width / imgUnscaledWidth; 579 final double scaleY = height / imgUnscaledHeight; 580 final double scale = Math.min(scaleX, scaleY); 581 tileWidth = Math.ceil(scale * imgUnscaledWidth); 582 tileHeight = Math.ceil(scale * imgUnscaledHeight); 583 } else if (bi.size.width >= 0 && bi.size.height >= 0) { 584 // The width and height have been expressly defined. Note that AUTO is -1, 585 // and all other negative values are disallowed, so by checking >= 0, we 586 // are essentially saying "if neither is AUTO" 587 tileWidth = w; 588 tileHeight = h; 589 } else if (w >= 0) { 590 // In this case, the width is specified, but the height is AUTO 591 tileWidth = w; 592 final double scale = tileWidth / imgUnscaledWidth; 593 tileHeight = imgUnscaledHeight * scale; 594 } else if (h >= 0) { 595 // Here the height is specified and the width is AUTO 596 tileHeight = h; 597 final double scale = tileHeight / imgUnscaledHeight; 598 tileWidth = imgUnscaledWidth * scale; 599 } else { 600 // Both are auto. 601 tileWidth = imgUnscaledWidth; 602 tileHeight = imgUnscaledHeight; 603 } 604 605 opaqueRegionTop = Double.isNaN(opaqueRegionTop) ? 0 : Math.min(0, opaqueRegionTop); 606 opaqueRegionRight = Double.isNaN(opaqueRegionRight) ? (width - tileWidth) : Math.min(width - tileWidth, opaqueRegionRight); 607 opaqueRegionBottom = Double.isNaN(opaqueRegionBottom) ? (height - tileHeight) : Math.min(height - tileHeight, opaqueRegionBottom); 608 opaqueRegionLeft = Double.isNaN(opaqueRegionLeft) ? 0 : Math.min(0, opaqueRegionLeft); 609 } 610 } 611 } 612 613 trbl[0] = opaqueRegionTop; 614 trbl[1] = opaqueRegionRight; 615 trbl[2] = opaqueRegionBottom; 616 trbl[3] = opaqueRegionLeft; 617 } 618 619 /** 620 * @inheritDoc 621 */ 622 @Override public boolean equals(Object o) { 623 if (this == o) return true; 624 if (o == null || getClass() != o.getClass()) return false; 625 Background that = (Background) o; 626 // Because the hash is cached, this can be a very fast check 627 if (hash != that.hash) return false; 628 if (!fills.equals(that.fills)) return false; 629 if (!images.equals(that.images)) return false; 630 631 return true; 632 } 633 634 /** 635 * @inheritDoc 636 */ 637 @Override public int hashCode() { 638 return hash; 639 } 640 }