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 * <p>The {@code LinearGradient} class fills a shape 037 * with a linear color gradient pattern. The user may specify two or 038 * more gradient colors, and this Paint will provide an interpolation 039 * between each color.</p> 040 * 041 * <p> 042 * The application provides an array of {@code Stop}s specifying how to distribute 043 * the colors along the gradient. The {@code Stop#offset} variable must be 044 * the range 0.0 to 1.0 and act like keyframes along the gradient. 045 * The offsets mark where the gradient should be exactly a particular color. </p> 046 * 047 * <p>If the proportional variable is set to true 048 * then the start and end points of the gradient 049 * should be specified relative to the unit square (0.0->1.0) and will 050 * be stretched across the shape. If the proportional variable is set 051 * to false, then the start and end points should be specified 052 * in the local coordinate system of the shape and the gradient will 053 * not be stretched at all.</p> 054 * 055 * <p> 056 * The two filled rectangles in the example below will render the same. 057 * The one on the left uses proportional coordinates to specify 058 * the end points of the gradient. The one on the right uses absolute 059 * coordinates. Both of them fill the specified rectangle with a 060 * horizontal gradient that varies from black to red</p> 061 * 062<PRE> 063// object bounding box relative (proportional = true) 064Stop[] stops = new Stop[] { new Stop(0, Color.BLACK), new Stop(1, Color.RED)}; 065LinearGradient lg1 = new LinearGradient(0, 0, 1, 0, true, CycleMethod.NO_CYCLE, stops); 066Rectangle r1 = new Rectangle(0, 0, 100, 100); 067r1.setFill(lg1); 068 069// user space relative (proportional: = false) 070LinearGradient lg2 = new LinearGradient(125, 0, 225, 0, false, CycleMethod.NO_CYCLE, stops); 071Rectangle r2 = new Rectangle(125, 0, 100, 100); 072r2.setFill(lg2); 073</PRE> 074 */ 075public final class LinearGradient extends Paint { 076 private double startX; 077 078 /** 079 * Defines the X coordinate of the gradient axis start point. 080 * If proportional is true (the default), this value specifies a 081 * point on a unit square that will be scaled to match the size of the 082 * the shape that the gradient fills. 083 ( 084 * @defaultValue 0.0 085 */ 086 public final double getStartX() { 087 return startX; 088 } 089 090 private double startY; 091 092 /** 093 * Defines the Y coordinate of the gradient axis start point. 094 * If proportional is true (the default), this value specifies a 095 * point on a unit square that will be scaled to match the size of the 096 * the shape that the gradient fills. 097 * 098 * @defaultValue 0.0 099 */ 100 public final double getStartY() { 101 return startY; 102 } 103 104 private double endX; 105 106 /** 107 * Defines the X coordinate of the gradient axis end point. 108 * If proportional is true (the default), this value specifies a 109 * point on a unit square that will be scaled to match the size of the 110 * the shape that the gradient fills. 111 * 112 * @defaultValue 1.0 113 */ 114 public final double getEndX() { 115 return endX; 116 } 117 118 private double endY; 119 120 /** 121 * Defines the Y coordinate of the gradient axis end point. 122 * If proportional is true (the default), this value specifies a 123 * point on a unit square that will be scaled to match the size of the 124 * the shape that the gradient fills. 125 * 126 * @defaultValue 1.0 127 */ 128 public final double getEndY() { 129 return endY; 130 } 131 132 private boolean proportional; 133 134 /** 135 * Indicates whether start and end locations are proportional or absolute. 136 * If this flag is true, the two end points are defined in a coordinate 137 * space where coordinates in the range {@code [0..1]} are scaled to map 138 * onto the bounds of the shape that the gradient fills. 139 * If this flag is false, then the coordinates are specified in the local 140 * coordinate system of the node. 141 * 142 * @defaultValue true 143 */ 144 public final boolean isProportional() { 145 return proportional; 146 } 147 148 private CycleMethod cycleMethod; 149 150 /** 151 * Defines which of the following cycle method is applied 152 * to the {@code LinearGradient}: {@code CycleMethod.NO_CYCLE}, 153 * {@code CycleMethod.REFLECT}, or {@code CycleMethod.REPEAT}. 154 * 155 * @defaultValue NO_CYCLE 156 */ 157 public final CycleMethod getCycleMethod() { 158 return cycleMethod; 159 } 160 161 private List<Stop> stops; 162 163 /** 164 * A sequence of 2 or more {@code Stop} values specifying how to distribute 165 * the colors along the gradient. These values must be in the range 166 * 0.0 to 1.0. They act like key frames along the gradient: they mark where 167 * the gradient should be exactly a particular color. 168 * 169 * <p>Each stop in the sequence must have an offset that is greater than the 170 * previous stop in the sequence.</p> 171 * 172 * <p>The list is unmodifiable and will throw 173 * {@code UnsupportedOperationException} on each modification attempt.</p> 174 * 175 * @defaultValue empty 176 */ 177 @ReturnsUnmodifiableCollection 178 public final List<Stop> getStops() { 179 return stops; 180 } 181 182 /** 183 * @inheritDoc 184 */ 185 @Override public final boolean isOpaque() { 186 return opaque; 187 } 188 189 private final boolean opaque; 190 191 /** 192 * A cached reference to the platform paint, no point recomputing twice 193 */ 194 private Object platformPaint; 195 196 /** 197 * The cached hash code, used to improve performance in situations where 198 * we cache gradients, such as in the CSS routines. 199 */ 200 private int hash; 201 202 /** 203 * Creates a new instance of LinearGradient. 204 * @param startX the X coordinate of the gradient axis start point 205 * @param startY the Y coordinate of the gradient axis start point 206 * @param endX the X coordinate of the gradient axis end point 207 * @param endY the Y coordinate of the gradient axis end point 208 * @param proportional whether the coordinates are proportional 209 * to the shape which this gradient fills 210 * @param cycleMethod cycle method applied to the gradient 211 * @param stops the gradient's color specification 212 */ 213 public LinearGradient( 214 double startX, 215 double startY, 216 @Default("1") double endX, 217 @Default("1") double endY, 218 @Default("true") boolean proportional, 219 CycleMethod cycleMethod, 220 Stop... stops) { 221 this.startX = startX; 222 this.startY = startY; 223 this.endX = endX; 224 this.endY = endY; 225 this.proportional = proportional; 226 this.cycleMethod = (cycleMethod == null) ? CycleMethod.NO_CYCLE: cycleMethod; 227 this.stops = Stop.normalize(stops); 228 this.opaque = determineOpacity(); 229 } 230 231 /** 232 * Creates a new instance of LinearGradient. 233 * @param startX the X coordinate of the gradient axis start point 234 * @param startY the Y coordinate of the gradient axis start point 235 * @param endX the X coordinate of the gradient axis end point 236 * @param endY the Y coordinate of the gradient axis end point 237 * @param proportional whether the coordinates are proportional 238 * to the shape which this gradient fills 239 * @param cycleMethod cycle method applied to the gradient 240 * @param stops the gradient's color specification 241 */ 242 public LinearGradient( 243 double startX, 244 double startY, 245 @Default("1") double endX, 246 @Default("1") double endY, 247 @Default("true") boolean proportional, 248 CycleMethod cycleMethod, 249 List<Stop> stops) { 250 this.startX = startX; 251 this.startY = startY; 252 this.endX = endX; 253 this.endY = endY; 254 this.proportional = proportional; 255 this.cycleMethod = (cycleMethod == null) ? CycleMethod.NO_CYCLE: cycleMethod; 256 this.stops = Stop.normalize(stops); 257 this.opaque = determineOpacity(); 258 } 259 260 /** 261 * Iterate over all the stops. If any one of them has a transparent 262 * color, then we return false. If there are no stops, we return false. 263 * Otherwise, we return true. Note that this is called AFTER Stop.normalize, 264 * which ensures that we always have at least 2 stops. 265 * 266 * @return Whether this gradient is opaque 267 */ 268 private boolean determineOpacity() { 269 final int numStops = this.stops.size(); 270 for (int i = 0; i < numStops; i++) { 271 if (!stops.get(i).getColor().isOpaque()) { 272 return false; 273 } 274 } 275 return true; 276 } 277 278 @Override 279 Object acc_getPlatformPaint() { 280 if (platformPaint == null) { 281 platformPaint = Toolkit.getToolkit().getPaint(this); 282 } 283 return platformPaint; 284 } 285 286 /** 287 * Indicates whether some other object is "equal to" this one. 288 * @param obj the reference object with which to compare. 289 * @return {@code true} if this object is equal to the {@code obj} argument; {@code false} otherwise. 290 */ 291 @Override public boolean equals(Object obj) { 292 if (obj == null) return false; 293 if (obj == this) return true; 294 if (obj instanceof LinearGradient) { 295 final LinearGradient other = (LinearGradient) obj; 296 if ((startX != other.startX) || 297 (startY != other.startY) || 298 (endX != other.endX) || 299 (endY != other.endY) || 300 (proportional != other.proportional) || 301 (cycleMethod != other.cycleMethod)) return false; 302 if (!stops.equals(other.stops)) return false; 303 return true; 304 } else return false; 305 } 306 307 /** 308 * Returns a hash code for this {@code LinearGradient} object. 309 * @return a hash code for this {@code LinearGradient} object. 310 */ 311 @Override public int hashCode() { 312 if (hash == 0) { 313 long bits = 17L; 314 bits = 37L * bits + Double.doubleToLongBits(startX); 315 bits = 37L * bits + Double.doubleToLongBits(startY); 316 bits = 37L * bits + Double.doubleToLongBits(endX); 317 bits = 37L * bits + Double.doubleToLongBits(endY); 318 bits = 37L * bits + ((proportional) ? 1231L : 1237L); 319 bits = 37L * bits + cycleMethod.hashCode(); 320 for (Stop stop: stops) { 321 bits = 37L * bits + stop.hashCode(); 322 } 323 hash = (int) (bits ^ (bits >> 32)); 324 } 325 return hash; 326 } 327 328 /** 329 * Returns a string representation of this {@code LinearGradient} object. 330 * @return a string representation of this {@code LinearGradient} object. 331 */ 332 @Override public String toString() { 333 final StringBuilder s = new StringBuilder("linear-gradient(from ") 334 .append(GradientUtils.lengthToString(startX, proportional)) 335 .append(" ").append(GradientUtils.lengthToString(startY, proportional)) 336 .append(" to ").append(GradientUtils.lengthToString(endX, proportional)) 337 .append(" ").append(GradientUtils.lengthToString(endY, proportional)) 338 .append(", "); 339 340 switch (cycleMethod) { 341 case REFLECT: 342 s.append("reflect").append(", "); 343 break; 344 case REPEAT: 345 s.append("repeat").append(", "); 346 break; 347 } 348 349 for (Stop stop : stops) { 350 s.append(stop).append(", "); 351 } 352 353 s.delete(s.length() - 2, s.length()); 354 s.append(")"); 355 356 return s.toString(); 357 } 358 359 /** 360 * Creates a linear gradient value from a string representation. 361 * <p>The format of the string representation is based on 362 * JavaFX CSS specification for linear gradient which is 363 * <pre> 364 * linear-gradient( [ [from <point> to <point>| [ to <side-or-corner>], ]? [ [ repeat | reflect ], ]? <color-stop>[, <color-stop>]+) 365 * </pre> 366 * where 367 * <pre> 368 * <side-or-corner> = [left | right] || [top | bottom] 369 * <point> = [ [ <length> <length> ] | [ <percentage> | <percentage> ] ] 370 * <color-stop> = [ <color> [ <percentage> | <length>]? ] 371 * </pre> 372 * </p> 373 * <p> Currently length can be only specified in px, the specification of unit can be omited. 374 * Format of color representation is the one used in {@link Color#web(String color)}. 375 * The linear-gradient keyword can be omited. 376 * For additional information about the format of string representation, see the 377 * <a href="../doc-files/cssref.html">CSS Reference Guide</a>. 378 * </p> 379 * 380 * Examples: 381 * <pre><code> 382 * LinearGradient g 383 * = LinearGradient.valueOf("linear-gradient(from 0% 0% to 100% 100%, red 0% , blue 30%, black 100%)"); 384 * LinearGradient g 385 * = LinearGradient.valueOf("from 0% 0% to 100% 100%, red 0% , blue 30%, black 100%"); 386 * LinearGradient g 387 * = LinearGradient.valueOf("linear-gradient(from 0px 0px to 200px 0px, #00ff00 0%, 0xff0000 50%, 0x1122ff40 100%)"); 388 * LinearGradient g 389 * = LinearGradient.valueOf("from 0px 0px to 200px 0px, #00ff00 0%, 0xff0000 50%, 0x1122ff40 100%"); 390 * </code></pre> 391 * 392 * @param value the string to convert 393 * @throws NullPointerException if the {@code value} is {@code null} 394 * @throws IllegalArgumentException if the {@code value} cannot be parsed 395 * @return a {@code LinearGradient} object holding the value represented 396 * by the string argument. 397 */ 398 public static LinearGradient valueOf(String value) { 399 if (value == null) { 400 throw new NullPointerException("gradient must be specified"); 401 } 402 403 String start = "linear-gradient("; 404 String end = ")"; 405 if (value.startsWith(start)) { 406 if (!value.endsWith(end)) { 407 throw new IllegalArgumentException("Invalid gradient specification, " 408 + "must end with \"" + end + '"'); 409 } 410 value = value.substring(start.length(), value.length() - end.length()); 411 } 412 413 GradientUtils.Parser parser = new GradientUtils.Parser(value); 414 if (parser.getSize() < 2) { 415 throw new IllegalArgumentException("Invalid gradient specification"); 416 } 417 418 GradientUtils.Point startX = GradientUtils.Point.MIN; 419 GradientUtils.Point startY = GradientUtils.Point.MIN; 420 GradientUtils.Point endX = GradientUtils.Point.MIN; 421 GradientUtils.Point endY = GradientUtils.Point.MIN; 422 423 String[] tokens = parser.splitCurrentToken(); 424 if ("from".equals(tokens[0])) { 425 GradientUtils.Parser.checkNumberOfArguments(tokens, 5); 426 startX = parser.parsePoint(tokens[1]); 427 startY = parser.parsePoint(tokens[2]); 428 if (!"to".equals(tokens[3])) { 429 throw new IllegalArgumentException("Invalid gradient specification, \"to\" expected"); 430 } 431 endX = parser.parsePoint(tokens[4]); 432 endY = parser.parsePoint(tokens[5]); 433 parser.shift(); 434 } else if ("to".equals(tokens[0])) { 435 int horizontalSet = 0; 436 int verticalSet = 0; 437 438 for (int i = 1; i < 3 && i < tokens.length; i++) { 439 if ("left".equals(tokens[i])) { 440 startX = GradientUtils.Point.MAX; 441 endX = GradientUtils.Point.MIN; 442 horizontalSet++; 443 } else if ("right".equals(tokens[i])) { 444 startX = GradientUtils.Point.MIN; 445 endX = GradientUtils.Point.MAX; 446 horizontalSet++; 447 } else if ("top".equals(tokens[i])) { 448 startY = GradientUtils.Point.MAX; 449 endY = GradientUtils.Point.MIN; 450 verticalSet++; 451 } else if ("bottom".equals(tokens[i])) { 452 startY = GradientUtils.Point.MIN; 453 endY = GradientUtils.Point.MAX; 454 verticalSet++; 455 } else { 456 throw new IllegalArgumentException("Invalid gradient specification," 457 + " unknown value after 'to'"); 458 } 459 } 460 if (verticalSet > 1) { 461 throw new IllegalArgumentException("Invalid gradient specification," 462 + " vertical direction set twice after 'to'"); 463 } 464 if (horizontalSet > 1) { 465 throw new IllegalArgumentException("Invalid gradient specification," 466 + " horizontal direction set twice after 'to'"); 467 } 468 parser.shift(); 469 } else { 470 // default is "to bottom" 471 startY = GradientUtils.Point.MIN; 472 endY = GradientUtils.Point.MAX; 473 } 474 475 // repeat/reflect 476 CycleMethod method = CycleMethod.NO_CYCLE; 477 String currentToken = parser.getCurrentToken(); 478 if ("repeat".equals(currentToken)) { 479 method = CycleMethod.REPEAT; 480 parser.shift(); 481 } else if ("reflect".equals(currentToken)) { 482 method = CycleMethod.REFLECT; 483 parser.shift(); 484 } 485 486 double dist = 0; 487 if (!startX.proportional) { 488 double dx = endX.value - startX.value; 489 double dy = endY.value - startY.value; 490 dist = Math.sqrt(dx*dx + dy*dy); 491 } 492 493 Stop[] stops = parser.parseStops(startX.proportional, dist); 494 495 return new LinearGradient(startX.value, startY.value, endX.value, endY.value, 496 startX.proportional, method, stops); 497 } 498}