Spec-Zone .ru
спецификации, руководства, описания, API
|
001/* 002 * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved. 003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 004 * 005 * This code is free software; you can redistribute it and/or modify it 006 * under the terms of the GNU General Public License version 2 only, as 007 * published by the Free Software Foundation. Oracle designates this 008 * particular file as subject to the "Classpath" exception as provided 009 * by Oracle in the LICENSE file that accompanied this code. 010 * 011 * This code is distributed in the hope that it will be useful, but WITHOUT 012 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 013 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 014 * version 2 for more details (a copy is included in the LICENSE file that 015 * accompanied this code). 016 * 017 * You should have received a copy of the GNU General Public License version 018 * 2 along with this work; if not, write to the Free Software Foundation, 019 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 020 * 021 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 022 * or visit www.oracle.com if you need additional information or have any 023 * questions. 024 */ 025 026package javafx.scene.paint; 027 028import java.util.List; 029 030import com.sun.javafx.beans.annotations.Default; 031import com.sun.javafx.collections.annotations.ReturnsUnmodifiableCollection; 032import com.sun.javafx.scene.paint.GradientUtils; 033import com.sun.javafx.tk.Toolkit; 034 035/** 036 * The {@code RadialGradient} class provides a way to fill a shape 037 * with a circular radial color gradient pattern. 038 * The user may specify 2 or more gradient colors, 039 * and this paint will provide an interpolation between each color. 040 * <p/> 041 * The user must specify the circle controlling the gradient pattern, 042 * which is defined by a center point and a radius. 043 * The user can also specify a separate focus point within that circle, 044 * which controls the location of the first color of the gradient. 045 * By default the focus is set to be the center of the circle. 046 * <p/> 047 * The center and radius are specified 048 * relative to a unit square, unless the <code>proportional</code> 049 * variable is false. By default proportional is true, and the 050 * gradient will be scaled to fill whatever shape it is applied to. 051 * The focus point is always specified relative to the center point 052 * by an angle and a distance relative to the radius. 053 * <p/> 054 * This paint will map the first color of the gradient to the focus point, 055 * and the last color to the perimeter of the circle, 056 * interpolating smoothly for any in-between colors specified by the user. 057 * Any line drawn from the focus point to the circumference will 058 * thus span all of the gradient colors. 059 * <p/> 060 * The focus distance will be clamped to the range {@code (-1, 1)} 061 * so that the focus point is always strictly inside the circle. 062 * <p/> 063 * The application provides an array of {@code Stop}s specifying how to distribute 064 * the colors along the gradient. The {@code Stop#offset} variable must be 065 * the range 0.0 to 1.0 and act like keyframes along the gradient. 066 * They mark where the gradient should be exactly a particular color. 067 */ 068public final class RadialGradient extends Paint { 069 private double focusAngle; 070 071 /** 072 * Defines the angle in degrees from the center of the gradient 073 * to the focus point to which the first color is mapped. 074 */ 075 public final double getFocusAngle() { 076 return focusAngle; 077 } 078 079 private double focusDistance; 080 081 /** 082 * Defines the distance from the center of the gradient to the 083 * focus point to which the first color is mapped. 084 * A distance of 0.0 will be at the center of the gradient circle. 085 * A distance of 1.0 will be on the circumference of the gradient circle. 086 */ 087 public final double getFocusDistance() { 088 return focusDistance; 089 } 090 091 private double centerX; 092 093 /** 094 * Defines the X coordinate of the center point of the circle defining the gradient. 095 * If proportional is true (the default), this value specifies a 096 * point on a unit square that will be scaled to match the size of the 097 * the shape that the gradient fills. 098 * The last color of the gradient is mapped to the perimeter of this circle. 099 * 100 * @defaultValue 0.0 101 */ 102 public final double getCenterX() { 103 return centerX; 104 } 105 106 private double centerY; 107 108 /** 109 * Defines the X coordinate of the center point of the circle defining the gradient. 110 * If proportional is true (the default), this value specifies a 111 * point on a unit square that will be scaled to match the size of the 112 * the shape that the gradient fills. 113 * The last color of the gradient is mapped to the perimeter of this circle. 114 * 115 * @defaultValue 0.0 116 */ 117 public final double getCenterY() { 118 return centerY; 119 } 120 121 private double radius; 122 123 /** 124 * Specifies the radius of the circle defining the extents of the color gradient. 125 * If proportional is true (the default), this value specifies a 126 * size relative to unit square that will be scaled to match the size of the 127 * the shape that the gradient fills. 128 * 129 * @defaultValue 1.0 130 */ 131 public final double getRadius() { 132 return radius; 133 } 134 135 private boolean proportional; 136 137 /** 138 * Indicates whether the center and radius values are proportional or 139 * absolute. 140 * If this flag is true, the center point and radius are defined 141 * in a coordinate space where coordinates in the range {@code [0..1]} 142 * are scaled to map onto the bounds of the shape that the gradient fills. 143 * If this flag is false, then the center coordinates and the radius are 144 * specified in the local coordinate system of the node. 145 * 146 * @defaultValue true 147 */ 148 public final boolean isProportional() { 149 return proportional; 150 } 151 152 private CycleMethod cycleMethod; 153 154 /** 155 * Defines which of the follwing cycle method is applied 156 * to the {@code LinearGradient}: {@code CycleMethod.NO_CYCLE}, 157 * {@code CycleMethod.REFLECT}, or {@code CycleMethod.REPEAT}. 158 * 159 * @defaultValue NO_CYCLE 160 */ 161 public final CycleMethod getCycleMethod() { 162 return cycleMethod; 163 } 164 165 private List<Stop> stops; 166 167 /** 168 * A sequence of 2 or more {@code Stop} values specifying how to distribute 169 * the colors along the gradient. These values must be in the range 170 * 0.0 to 1.0. They act like keyframes along the gradient: they mark where the 171 * gradient should be exactly a particular color. 172 * 173 * <p>Each stop in the sequence must have an offset that is greater than the previous 174 * stop in the sequence.</p> 175 * 176 * <p>The list is unmodifiable and will throw 177 * {@code UnsupportedOperationException} on each modification attempt.</p> 178 * 179 * @defaultValue empty 180 */ 181 @ReturnsUnmodifiableCollection 182 public final List<Stop> getStops() { 183 return stops; 184 } 185 186 /** 187 * @inheritDoc 188 */ 189 @Override public final boolean isOpaque() { 190 return opaque; 191 } 192 193 private final boolean opaque; 194 195 /** 196 * A cached reference to the platform paint, no point recomputing twice 197 */ 198 private Object platformPaint; 199 200 /** 201 * The cached hash code, used to improve performance in situations where 202 * we cache gradients, such as in the CSS routines. 203 */ 204 private int hash; 205 206 /** 207 * Creates a new instance of RadialGradient. 208 * @param focusAngle the angle in degrees from the center of the gradient 209 * to the focus point to which the first color is mapped 210 * @param focusDistance the distance from the center of the gradient to the 211 * focus point to which the first color is mapped 212 * @param centerX the X coordinate of the center point of the gradient's circle 213 * @param centerY the Y coordinate of the center point of the gradient's circle 214 * @param radius the radius of the circle defining the extents of the color gradient 215 * @param proportional whether the coordinates and sizes are proportional 216 * to the shape which this gradient fills 217 * @param cycleMethod cycle method applied to the gradient 218 * @param stops the gradient's color specification 219 */ 220 public RadialGradient( 221 double focusAngle, 222 double focusDistance, 223 double centerX, 224 double centerY, 225 @Default("1") double radius, 226 @Default("true") boolean proportional, 227 CycleMethod cycleMethod, 228 Stop... stops) { 229 this.focusAngle = focusAngle; 230 this.focusDistance = focusDistance; 231 this.centerX = centerX; 232 this.centerY = centerY; 233 this.radius = radius; 234 this.proportional = proportional; 235 this.cycleMethod = (cycleMethod == null) ? CycleMethod.NO_CYCLE : cycleMethod; 236 this.stops = Stop.normalize(stops); 237 this.opaque = determineOpacity(); 238 } 239 240 /** 241 * Creates a new instance of RadialGradient. 242 * @param focusAngle the angle in degrees from the center of the gradient 243 * to the focus point to which the first color is mapped 244 * @param focusDistance the distance from the center of the gradient to the 245 * focus point to which the first color is mapped 246 * @param centerX the X coordinate of the center point of the gradient's circle 247 * @param centerY the Y coordinate of the center point of the gradient's circle 248 * @param radius the radius of the circle defining the extents of the color gradient 249 * @param proportional whether the coordinates and sizes are proportional 250 * to the shape which this gradient fills 251 * @param cycleMethod cycle method applied to the gradient 252 * @param stops the gradient's color specification 253 */ 254 public RadialGradient( 255 double focusAngle, 256 double focusDistance, 257 double centerX, 258 double centerY, 259 @Default("1") double radius, 260 @Default("true") boolean proportional, 261 CycleMethod cycleMethod, 262 List<Stop> stops) { 263 this.focusAngle = focusAngle; 264 this.focusDistance = focusDistance; 265 this.centerX = centerX; 266 this.centerY = centerY; 267 this.radius = radius; 268 this.proportional = proportional; 269 this.cycleMethod = (cycleMethod == null) ? CycleMethod.NO_CYCLE : cycleMethod; 270 this.stops = Stop.normalize(stops); 271 this.opaque = determineOpacity(); 272 } 273 274 /** 275 * Iterate over all the stops. If any one of them has a transparent 276 * color, then we return false. If there are no stops, we return false. 277 * Otherwise, we return true. Note that this is called AFTER Stop.normalize, 278 * which ensures that we always have at least 2 stops. 279 * 280 * @return Whether this gradient is opaque 281 */ 282 private boolean determineOpacity() { 283 final int numStops = this.stops.size(); 284 for (int i = 0; i < numStops; i++) { 285 if (!stops.get(i).getColor().isOpaque()) { 286 return false; 287 } 288 } 289 return true; 290 } 291 292 @Override 293 Object acc_getPlatformPaint() { 294 if (platformPaint == null) { 295 platformPaint = Toolkit.getToolkit().getPaint(this); 296 } 297 return platformPaint; 298 } 299 300 /** 301 * Indicates whether some other object is "equal to" this one. 302 * @param obj the reference object with which to compare. 303 * @return {@code true} if this object is equal to the {@code obj} argument; {@code false} otherwise. 304 */ 305 @Override public boolean equals(Object obj) { 306 if (obj == this) return true; 307 if (obj instanceof RadialGradient) { 308 final RadialGradient other = (RadialGradient) obj; 309 if ((focusAngle != other.focusAngle) || 310 (focusDistance != other.focusDistance) || 311 (centerX != other.centerX) || 312 (centerY != other.centerY) || 313 (radius != other.radius) || 314 (proportional != other.proportional) || 315 (cycleMethod != other.cycleMethod)) return false; 316 if (!stops.equals(other.stops)) return false; 317 return true; 318 } else return false; 319 } 320 321 /** 322 * Returns a hash code for this {@code RadialGradient} object. 323 * @return a hash code for this {@code RadialGradient} object. 324 */ 325 @Override public int hashCode() { 326 // We should be able to just call focusAngle.hashCode(), 327 // see http://javafx-jira.kenai.com/browse/JFXC-4247 328 if (hash == 0) { 329 long bits = 17; 330 bits = 37 * bits + Double.doubleToLongBits(focusAngle); 331 bits = 37 * bits + Double.doubleToLongBits(focusDistance); 332 bits = 37 * bits + Double.doubleToLongBits(centerX); 333 bits = 37 * bits + Double.doubleToLongBits(centerY); 334 bits = 37 * bits + Double.doubleToLongBits(radius); 335 bits = 37 * bits + (proportional ? 1231 : 1237); 336 bits = 37 * bits + cycleMethod.hashCode(); 337 for (Stop stop: stops) { 338 bits = 37 * bits + stop.hashCode(); 339 } 340 hash = (int) (bits ^ (bits >> 32)); 341 } 342 return hash; 343 } 344 345 /** 346 * Returns a string representation of this {@code RadialGradient} object. 347 * @return a string representation of this {@code RadialGradient} object. 348 */ 349 @Override public String toString() { 350 final StringBuilder s = new StringBuilder("radial-gradient(focus-angle ").append(focusAngle) 351 .append("deg, focus-distance ").append(focusDistance * 100) 352 .append("% , center ").append(GradientUtils.lengthToString(centerX, proportional)) 353 .append(" ").append(GradientUtils.lengthToString(centerY, proportional)) 354 .append(", radius ").append(GradientUtils.lengthToString(radius, proportional)) 355 .append(", "); 356 357 switch (cycleMethod) { 358 case REFLECT: 359 s.append("reflect").append(", "); 360 break; 361 case REPEAT: 362 s.append("repeat").append(", "); 363 break; 364 } 365 366 for (Stop stop : stops) { 367 s.append(stop).append(", "); 368 } 369 370 s.delete(s.length() - 2, s.length()); 371 s.append(")"); 372 373 return s.toString(); 374 } 375 376 /** 377 * Creates a radial gradient value from a string representation. 378 * <p>The format of the string representation is based on 379 * JavaFX CSS specification for radial gradient which is 380 * <pre> 381 * radial-gradient([focus-angle <angle>, ]? 382 * [focus-distance <percentage>, ]? 383 * [center <point>, ]? 384 * radius [<length> | <percentage>], 385 * [[repeat | reflect],]? 386 * <color-stop>[, <color-stop>]+) 387 * </pre> 388 * where 389 * <pre> 390 * <point> = [ [ <length> <length> ] | [ <percentage> | <percentage> ] ] 391 * <color-stop> = [ <color> [ <percentage> | <length>]? ] 392 * </pre> 393 * </p> 394 * <p>Currently length can be only specified in px, the specification of unit can be omited. 395 * Format of color representation is the one used in {@link Color#web(String color)}. 396 * The radial-gradient keyword can be omited. 397 * For additional information about the format of string representation, see the 398 * <a href="../doc-files/cssref.html">CSS Reference Guide</a>. 399 * </p> 400 * 401 * Examples: 402 * <pre><code> 403 * RadialGradient g 404 * = RadialGradient.valueOf("radial-gradient(center 100px 100px, radius 200px, red 0%, blue 30%, black 100%)"); 405 * RadialGradient g 406 * = RadialGradient.valueOf("center 100px 100px, radius 200px, red 0%, blue 30%, black 100%"); 407 * RadialGradient g 408 * = RadialGradient.valueOf("radial-gradient(center 50% 50%, radius 50%, cyan, violet 75%, magenta)"); 409 * RadialGradient g 410 * = RadialGradient.valueOf("center 50% 50%, radius 50%, cyan, violet 75%, magenta"); 411 * </code></pre> 412 * 413 * @param value the string to convert 414 * @throws NullPointerException if the {@code value} is {@code null} 415 * @throws IllegalArgumentException if the {@code value} cannot be parsed 416 * @return a {@code RadialGradient} object holding the value represented 417 * by the string argument. 418 */ 419 public static RadialGradient valueOf(String value) { 420 if (value == null) { 421 throw new NullPointerException("gradient must be specified"); 422 } 423 424 String start = "radial-gradient("; 425 String end = ")"; 426 if (value.startsWith(start)) { 427 if (!value.endsWith(end)) { 428 throw new IllegalArgumentException("Invalid gradient specification," 429 + " must end with \"" + end + '"'); 430 } 431 value = value.substring(start.length(), value.length() - end.length()); 432 } 433 434 GradientUtils.Parser parser = new GradientUtils.Parser(value); 435 if (parser.getSize() < 2) { 436 throw new IllegalArgumentException("Invalid gradient specification"); 437 } 438 439 double angle = 0, distance = 0; 440 GradientUtils.Point centerX, centerY, radius; 441 442 String[] tokens = parser.splitCurrentToken(); 443 if ("focus-angle".equals(tokens[0])) { 444 GradientUtils.Parser.checkNumberOfArguments(tokens, 1); 445 angle = GradientUtils.Parser.parseAngle(tokens[1]); 446 parser.shift(); 447 } 448 449 tokens = parser.splitCurrentToken(); 450 if ("focus-distance".equals(tokens[0])) { 451 GradientUtils.Parser.checkNumberOfArguments(tokens, 1); 452 distance = GradientUtils.Parser.parsePercentage(tokens[1]); 453 454 parser.shift(); 455 } 456 457 tokens = parser.splitCurrentToken(); 458 if ("center".equals(tokens[0])) { 459 GradientUtils.Parser.checkNumberOfArguments(tokens, 2); 460 centerX = parser.parsePoint(tokens[1]); 461 centerY = parser.parsePoint(tokens[2]); 462 parser.shift(); 463 } else { 464 centerX = GradientUtils.Point.MIN; 465 centerY = GradientUtils.Point.MIN; 466 } 467 468 tokens = parser.splitCurrentToken(); 469 if ("radius".equals(tokens[0])) { 470 GradientUtils.Parser.checkNumberOfArguments(tokens, 1); 471 radius = parser.parsePoint(tokens[1]); 472 parser.shift(); 473 } else { 474 throw new IllegalArgumentException("Invalid gradient specification: " 475 + "radius must be specified"); 476 } 477 478 CycleMethod method = CycleMethod.NO_CYCLE; 479 String currentToken = parser.getCurrentToken(); 480 if ("repeat".equals(currentToken)) { 481 method = CycleMethod.REPEAT; 482 parser.shift(); 483 } else if ("reflect".equals(currentToken)) { 484 method = CycleMethod.REFLECT; 485 parser.shift(); 486 } 487 488 Stop[] stops = parser.parseStops(radius.proportional, radius.value); 489 490 return new RadialGradient(angle, distance, centerX.value, centerY.value, 491 radius.value, radius.proportional, method, stops); 492 } 493 494}