CSSGradientValue.cpp revision 65f03d4f644ce73618e5f4f50dd694b26f55ae12
1/* 2 * Copyright (C) 2008 Apple Inc. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions 6 * are met: 7 * 1. Redistributions of source code must retain the above copyright 8 * notice, this list of conditions and the following disclaimer. 9 * 2. Redistributions in binary form must reproduce the above copyright 10 * notice, this list of conditions and the following disclaimer in the 11 * documentation and/or other materials provided with the distribution. 12 * 13 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY 14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR 17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 */ 25 26#include "config.h" 27#include "CSSGradientValue.h" 28 29#include "CSSValueKeywords.h" 30#include "CSSStyleSelector.h" 31#include "GeneratedImage.h" 32#include "Gradient.h" 33#include "Image.h" 34#include "IntSize.h" 35#include "IntSizeHash.h" 36#include "NodeRenderStyle.h" 37#include "PlatformString.h" 38#include "RenderObject.h" 39 40using namespace std; 41 42namespace WebCore { 43 44Image* CSSGradientValue::image(RenderObject* renderer, const IntSize& size) 45{ 46 if (!m_clients.contains(renderer)) 47 return 0; 48 49 // Need to look up our size. Create a string of width*height to use as a hash key. 50 // FIXME: hashing based only on size is not sufficient. Color stops may use context-sensitive units (like em) 51 // that should force the color stop positions to be recomputed. 52 Image* result = getImage(renderer, size); 53 if (result) 54 return result; 55 56 if (size.isEmpty()) 57 return 0; 58 59 // We need to create an image. 60 RefPtr<Image> newImage = GeneratedImage::create(createGradient(renderer, size), size); 61 result = newImage.get(); 62 putImage(size, newImage.release()); 63 64 return result; 65} 66 67// Should only ever be called for deprecated gradients. 68static inline bool compareStops(const CSSGradientColorStop& a, const CSSGradientColorStop& b) 69{ 70 double aVal = a.m_position->getDoubleValue(CSSPrimitiveValue::CSS_NUMBER); 71 double bVal = b.m_position->getDoubleValue(CSSPrimitiveValue::CSS_NUMBER); 72 73 return aVal < bVal; 74} 75 76void CSSGradientValue::sortStopsIfNeeded() 77{ 78 ASSERT(m_deprecatedType); 79 if (!m_stopsSorted) { 80 if (m_stops.size()) 81 std::stable_sort(m_stops.begin(), m_stops.end(), compareStops); 82 m_stopsSorted = true; 83 } 84} 85 86static inline int blend(int from, int to, float progress) 87{ 88 return int(from + (to - from) * progress); 89} 90 91static inline Color blend(const Color& from, const Color& to, float progress) 92{ 93 // FIXME: when we interpolate gradients using premultiplied colors, this should also do premultiplication. 94 return Color(blend(from.red(), to.red(), progress), 95 blend(from.green(), to.green(), progress), 96 blend(from.blue(), to.blue(), progress), 97 blend(from.alpha(), to.alpha(), progress)); 98} 99 100struct GradientStop { 101 Color color; 102 float offset; 103 bool specified; 104 105 GradientStop() 106 : offset(0) 107 , specified(false) 108 { } 109}; 110 111void CSSGradientValue::addStops(Gradient* gradient, RenderObject* renderer, RenderStyle* rootStyle, float maxLengthForRepeat) 112{ 113 RenderStyle* style = renderer->style(); 114 115 if (m_deprecatedType) { 116 sortStopsIfNeeded(); 117 118 // We have to resolve colors. 119 for (unsigned i = 0; i < m_stops.size(); i++) { 120 const CSSGradientColorStop& stop = m_stops[i]; 121 Color color = renderer->document()->styleSelector()->getColorFromPrimitiveValue(stop.m_color.get()); 122 123 float offset; 124 if (stop.m_position->primitiveType() == CSSPrimitiveValue::CSS_PERCENTAGE) 125 offset = stop.m_position->getFloatValue(CSSPrimitiveValue::CSS_PERCENTAGE) / 100; 126 else 127 offset = stop.m_position->getFloatValue(CSSPrimitiveValue::CSS_NUMBER); 128 129 gradient->addColorStop(offset, color); 130 } 131 132 // The back end already sorted the stops. 133 gradient->setStopsSorted(true); 134 return; 135 } 136 137 size_t numStops = m_stops.size(); 138 139 Vector<GradientStop> stops(numStops); 140 141 float gradientLength = 0; 142 bool computedGradientLength = false; 143 144 FloatPoint gradientStart = gradient->p0(); 145 FloatPoint gradientEnd; 146 if (isLinearGradient()) 147 gradientEnd = gradient->p1(); 148 else if (isRadialGradient()) 149 gradientEnd = gradientStart + FloatSize(gradient->endRadius(), 0); 150 151 for (size_t i = 0; i < numStops; ++i) { 152 const CSSGradientColorStop& stop = m_stops[i]; 153 154 stops[i].color = renderer->document()->styleSelector()->getColorFromPrimitiveValue(stop.m_color.get()); 155 156 if (stop.m_position) { 157 int type = stop.m_position->primitiveType(); 158 if (type == CSSPrimitiveValue::CSS_PERCENTAGE) 159 stops[i].offset = stop.m_position->getFloatValue(CSSPrimitiveValue::CSS_PERCENTAGE) / 100; 160 else if (CSSPrimitiveValue::isUnitTypeLength(type)) { 161 float length = stop.m_position->computeLengthFloat(style, rootStyle, style->effectiveZoom()); 162 if (!computedGradientLength) { 163 FloatSize gradientSize(gradientStart - gradientEnd); 164 gradientLength = gradientSize.diagonalLength(); 165 } 166 stops[i].offset = (gradientLength > 0) ? length / gradientLength : 0; 167 } else { 168 ASSERT_NOT_REACHED(); 169 stops[i].offset = 0; 170 } 171 stops[i].specified = true; 172 } else { 173 // If the first color-stop does not have a position, its position defaults to 0%. 174 // If the last color-stop does not have a position, its position defaults to 100%. 175 if (!i) { 176 stops[i].offset = 0; 177 stops[i].specified = true; 178 } else if (numStops > 1 && i == numStops - 1) { 179 stops[i].offset = 1; 180 stops[i].specified = true; 181 } 182 } 183 184 // If a color-stop has a position that is less than the specified position of any 185 // color-stop before it in the list, its position is changed to be equal to the 186 // largest specified position of any color-stop before it. 187 if (stops[i].specified && i > 0) { 188 size_t prevSpecifiedIndex; 189 for (prevSpecifiedIndex = i - 1; prevSpecifiedIndex; --prevSpecifiedIndex) { 190 if (stops[prevSpecifiedIndex].specified) 191 break; 192 } 193 194 if (stops[i].offset < stops[prevSpecifiedIndex].offset) 195 stops[i].offset = stops[prevSpecifiedIndex].offset; 196 } 197 } 198 199 ASSERT(stops[0].specified && stops[numStops - 1].specified); 200 201 // If any color-stop still does not have a position, then, for each run of adjacent 202 // color-stops without positions, set their positions so that they are evenly spaced 203 // between the preceding and following color-stops with positions. 204 if (numStops > 2) { 205 size_t unspecifiedRunStart = 0; 206 bool inUnspecifiedRun = false; 207 208 for (size_t i = 0; i < numStops; ++i) { 209 if (!stops[i].specified && !inUnspecifiedRun) { 210 unspecifiedRunStart = i; 211 inUnspecifiedRun = true; 212 } else if (stops[i].specified && inUnspecifiedRun) { 213 size_t unspecifiedRunEnd = i; 214 215 if (unspecifiedRunStart < unspecifiedRunEnd) { 216 float lastSpecifiedOffset = stops[unspecifiedRunStart - 1].offset; 217 float nextSpecifiedOffset = stops[unspecifiedRunEnd].offset; 218 float delta = (nextSpecifiedOffset - lastSpecifiedOffset) / (unspecifiedRunEnd - unspecifiedRunStart + 1); 219 220 for (size_t j = unspecifiedRunStart; j < unspecifiedRunEnd; ++j) 221 stops[j].offset = lastSpecifiedOffset + (j - unspecifiedRunStart + 1) * delta; 222 } 223 224 inUnspecifiedRun = false; 225 } 226 } 227 } 228 229 // If the gradient is repeating, repeat the color stops. 230 // We can't just push this logic down into the platform-specific Gradient code, 231 // because we have to know the extent of the gradient, and possible move the end points. 232 if (m_repeating && numStops > 1) { 233 float maxExtent = 1; 234 235 // Radial gradients may need to extend further than the endpoints, because they have 236 // to repeat out to the corners of the box. 237 if (isRadialGradient()) { 238 if (!computedGradientLength) { 239 FloatSize gradientSize(gradientStart - gradientEnd); 240 gradientLength = gradientSize.diagonalLength(); 241 } 242 243 if (maxLengthForRepeat > gradientLength) 244 maxExtent = maxLengthForRepeat / gradientLength; 245 } 246 247 size_t originalNumStops = numStops; 248 size_t originalFirstStopIndex = 0; 249 250 // Work backwards from the first, adding stops until we get one before 0. 251 float firstOffset = stops[0].offset; 252 if (firstOffset > 0) { 253 float currOffset = firstOffset; 254 size_t srcStopOrdinal = originalNumStops - 1; 255 256 while (true) { 257 GradientStop newStop = stops[originalFirstStopIndex + srcStopOrdinal]; 258 newStop.offset = currOffset; 259 stops.prepend(newStop); 260 ++originalFirstStopIndex; 261 if (currOffset < 0) 262 break; 263 264 if (srcStopOrdinal) 265 currOffset -= stops[originalFirstStopIndex + srcStopOrdinal].offset - stops[originalFirstStopIndex + srcStopOrdinal - 1].offset; 266 srcStopOrdinal = (srcStopOrdinal + originalNumStops - 1) % originalNumStops; 267 } 268 } 269 270 // Work forwards from the end, adding stops until we get one after 1. 271 float lastOffset = stops[stops.size() - 1].offset; 272 if (lastOffset < maxExtent) { 273 float currOffset = lastOffset; 274 size_t srcStopOrdinal = 0; 275 276 while (true) { 277 GradientStop newStop = stops[srcStopOrdinal]; 278 newStop.offset = currOffset; 279 stops.append(newStop); 280 if (currOffset > maxExtent) 281 break; 282 if (srcStopOrdinal < originalNumStops - 1) 283 currOffset += stops[originalFirstStopIndex + srcStopOrdinal + 1].offset - stops[originalFirstStopIndex + srcStopOrdinal].offset; 284 srcStopOrdinal = (srcStopOrdinal + 1) % originalNumStops; 285 } 286 } 287 } 288 289 numStops = stops.size(); 290 291 // If the gradient goes outside the 0-1 range, normalize it by moving the endpoints, and adjusting the stops. 292 if (numStops > 1 && (stops[0].offset < 0 || stops[numStops - 1].offset > 1)) { 293 if (isLinearGradient()) { 294 float firstOffset = stops[0].offset; 295 float lastOffset = stops[numStops - 1].offset; 296 float scale = lastOffset - firstOffset; 297 298 for (size_t i = 0; i < numStops; ++i) 299 stops[i].offset = (stops[i].offset - firstOffset) / scale; 300 301 FloatPoint p0 = gradient->p0(); 302 FloatPoint p1 = gradient->p1(); 303 gradient->setP0(FloatPoint(p0.x() + firstOffset * (p1.x() - p0.x()), p0.y() + firstOffset * (p1.y() - p0.y()))); 304 gradient->setP1(FloatPoint(p1.x() + (lastOffset - 1) * (p1.x() - p0.x()), p1.y() + (lastOffset - 1) * (p1.y() - p0.y()))); 305 } else if (isRadialGradient()) { 306 // Rather than scaling the points < 0, we truncate them, so only scale according to the largest point. 307 float firstOffset = 0; 308 float lastOffset = stops[numStops - 1].offset; 309 float scale = lastOffset - firstOffset; 310 311 // Reset points below 0 to the first visible color. 312 size_t firstZeroOrGreaterIndex = numStops; 313 for (size_t i = 0; i < numStops; ++i) { 314 if (stops[i].offset >= 0) { 315 firstZeroOrGreaterIndex = i; 316 break; 317 } 318 } 319 320 if (firstZeroOrGreaterIndex > 0) { 321 if (firstZeroOrGreaterIndex < numStops && stops[firstZeroOrGreaterIndex].offset > 0) { 322 float prevOffset = stops[firstZeroOrGreaterIndex - 1].offset; 323 float nextOffset = stops[firstZeroOrGreaterIndex].offset; 324 325 float interStopProportion = -prevOffset / (nextOffset - prevOffset); 326 Color blendedColor = blend(stops[firstZeroOrGreaterIndex - 1].color, stops[firstZeroOrGreaterIndex].color, interStopProportion); 327 328 // Clamp the positions to 0 and set the color. 329 for (size_t i = 0; i < firstZeroOrGreaterIndex; ++i) { 330 stops[i].offset = 0; 331 stops[i].color = blendedColor; 332 } 333 } else { 334 // All stops are below 0; just clamp them. 335 for (size_t i = 0; i < firstZeroOrGreaterIndex; ++i) 336 stops[i].offset = 0; 337 } 338 } 339 340 for (size_t i = 0; i < numStops; ++i) 341 stops[i].offset /= scale; 342 343 gradient->setStartRadius(gradient->startRadius() * scale); 344 gradient->setEndRadius(gradient->endRadius() * scale); 345 } 346 } 347 348 for (unsigned i = 0; i < numStops; i++) 349 gradient->addColorStop(stops[i].offset, stops[i].color); 350 351 gradient->setStopsSorted(true); 352} 353 354static float positionFromValue(CSSPrimitiveValue* value, RenderStyle* style, RenderStyle* rootStyle, const IntSize& size, bool isHorizontal) 355{ 356 float zoomFactor = style->effectiveZoom(); 357 358 switch (value->primitiveType()) { 359 case CSSPrimitiveValue::CSS_NUMBER: 360 return value->getFloatValue() * zoomFactor; 361 362 case CSSPrimitiveValue::CSS_PERCENTAGE: 363 return value->getFloatValue() / 100.f * (isHorizontal ? size.width() : size.height()); 364 365 case CSSPrimitiveValue::CSS_IDENT: 366 switch (value->getIdent()) { 367 case CSSValueTop: 368 ASSERT(!isHorizontal); 369 return 0; 370 case CSSValueLeft: 371 ASSERT(isHorizontal); 372 return 0; 373 case CSSValueBottom: 374 ASSERT(!isHorizontal); 375 return size.height(); 376 case CSSValueRight: 377 ASSERT(isHorizontal); 378 return size.width(); 379 } 380 381 default: 382 return value->computeLengthFloat(style, rootStyle, zoomFactor); 383 } 384} 385 386FloatPoint CSSGradientValue::computeEndPoint(CSSPrimitiveValue* first, CSSPrimitiveValue* second, RenderStyle* style, RenderStyle* rootStyle, const IntSize& size) 387{ 388 FloatPoint result; 389 390 if (first) 391 result.setX(positionFromValue(first, style, rootStyle, size, true)); 392 393 if (second) 394 result.setY(positionFromValue(second, style, rootStyle, size, false)); 395 396 return result; 397} 398 399String CSSLinearGradientValue::cssText() const 400{ 401 String result; 402 if (m_deprecatedType) { 403 result = "-webkit-gradient(linear, "; 404 result += m_firstX->cssText() + " "; 405 result += m_firstY->cssText() + ", "; 406 result += m_secondX->cssText() + " "; 407 result += m_secondY->cssText(); 408 409 for (unsigned i = 0; i < m_stops.size(); i++) { 410 const CSSGradientColorStop& stop = m_stops[i]; 411 result += ", "; 412 if (stop.m_position->getDoubleValue(CSSPrimitiveValue::CSS_NUMBER) == 0) 413 result += "from(" + stop.m_color->cssText() + ")"; 414 else if (stop.m_position->getDoubleValue(CSSPrimitiveValue::CSS_NUMBER) == 1) 415 result += "to(" + stop.m_color->cssText() + ")"; 416 else 417 result += "color-stop(" + String::number(stop.m_position->getDoubleValue(CSSPrimitiveValue::CSS_NUMBER)) + ", " + stop.m_color->cssText() + ")"; 418 } 419 } else { 420 result = m_repeating ? "-webkit-repeating-linear-gradient(" : "-webkit-linear-gradient("; 421 if (m_angle) 422 result += m_angle->cssText(); 423 else { 424 if (m_firstX && m_firstY) 425 result += m_firstX->cssText() + " " + m_firstY->cssText(); 426 else if (m_firstX || m_firstY) { 427 if (m_firstX) 428 result += m_firstX->cssText(); 429 430 if (m_firstY) 431 result += m_firstY->cssText(); 432 } 433 } 434 435 for (unsigned i = 0; i < m_stops.size(); i++) { 436 const CSSGradientColorStop& stop = m_stops[i]; 437 result += ", "; 438 result += stop.m_color->cssText(); 439 if (stop.m_position) 440 result += " " + stop.m_position->cssText(); 441 } 442 } 443 444 result += ")"; 445 return result; 446} 447 448// Compute the endpoints so that a gradient of the given angle covers a box of the given size. 449static void endPointsFromAngle(float angleDeg, const IntSize& size, FloatPoint& firstPoint, FloatPoint& secondPoint) 450{ 451 angleDeg = fmodf(angleDeg, 360); 452 if (angleDeg < 0) 453 angleDeg += 360; 454 455 if (!angleDeg) { 456 firstPoint.set(0, 0); 457 secondPoint.set(size.width(), 0); 458 return; 459 } 460 461 if (angleDeg == 90) { 462 firstPoint.set(0, size.height()); 463 secondPoint.set(0, 0); 464 return; 465 } 466 467 if (angleDeg == 180) { 468 firstPoint.set(size.width(), 0); 469 secondPoint.set(0, 0); 470 return; 471 } 472 473 float slope = tan(deg2rad(angleDeg)); 474 475 // We find the endpoint by computing the intersection of the line formed by the slope, 476 // and a line perpendicular to it that intersects the corner. 477 float perpendicularSlope = -1 / slope; 478 479 // Compute start corner relative to center. 480 float halfHeight = size.height() / 2; 481 float halfWidth = size.width() / 2; 482 FloatPoint endCorner; 483 if (angleDeg < 90) 484 endCorner.set(halfWidth, halfHeight); 485 else if (angleDeg < 180) 486 endCorner.set(-halfWidth, halfHeight); 487 else if (angleDeg < 270) 488 endCorner.set(-halfWidth, -halfHeight); 489 else 490 endCorner.set(halfWidth, -halfHeight); 491 492 // Compute c (of y = mx + c) using the corner point. 493 float c = endCorner.y() - perpendicularSlope * endCorner.x(); 494 float endX = c / (slope - perpendicularSlope); 495 float endY = perpendicularSlope * endX + c; 496 497 // We computed the end point, so set the second point, flipping the Y to account for angles going anticlockwise. 498 secondPoint.set(halfWidth + endX, size.height() - (halfHeight + endY)); 499 // Reflect around the center for the start point. 500 firstPoint.set(size.width() - secondPoint.x(), size.height() - secondPoint.y()); 501} 502 503PassRefPtr<Gradient> CSSLinearGradientValue::createGradient(RenderObject* renderer, const IntSize& size) 504{ 505 ASSERT(!size.isEmpty()); 506 507 RenderStyle* rootStyle = renderer->document()->documentElement()->renderStyle(); 508 509 FloatPoint firstPoint; 510 FloatPoint secondPoint; 511 if (m_angle) { 512 float angle = m_angle->getFloatValue(CSSPrimitiveValue::CSS_DEG); 513 endPointsFromAngle(angle, size, firstPoint, secondPoint); 514 } else { 515 firstPoint = computeEndPoint(m_firstX.get(), m_firstY.get(), renderer->style(), rootStyle, size); 516 517 if (m_secondX || m_secondY) 518 secondPoint = computeEndPoint(m_secondX.get(), m_secondY.get(), renderer->style(), rootStyle, size); 519 else { 520 if (m_firstX) 521 secondPoint.setX(size.width() - firstPoint.x()); 522 if (m_firstY) 523 secondPoint.setY(size.height() - firstPoint.y()); 524 } 525 } 526 527 RefPtr<Gradient> gradient = Gradient::create(firstPoint, secondPoint); 528 529 // Now add the stops. 530 addStops(gradient.get(), renderer, rootStyle, 1); 531 532 return gradient.release(); 533} 534 535String CSSRadialGradientValue::cssText() const 536{ 537 String result; 538 539 if (m_deprecatedType) { 540 result = "-webkit-gradient(radial, "; 541 542 result += m_firstX->cssText() + " "; 543 result += m_firstY->cssText() + ", "; 544 result += m_firstRadius->cssText() + ", "; 545 result += m_secondX->cssText() + " "; 546 result += m_secondY->cssText(); 547 result += ", "; 548 result += m_secondRadius->cssText(); 549 550 // FIXME: share? 551 for (unsigned i = 0; i < m_stops.size(); i++) { 552 const CSSGradientColorStop& stop = m_stops[i]; 553 result += ", "; 554 if (stop.m_position->getDoubleValue(CSSPrimitiveValue::CSS_NUMBER) == 0) 555 result += "from(" + stop.m_color->cssText() + ")"; 556 else if (stop.m_position->getDoubleValue(CSSPrimitiveValue::CSS_NUMBER) == 1) 557 result += "to(" + stop.m_color->cssText() + ")"; 558 else 559 result += "color-stop(" + String::number(stop.m_position->getDoubleValue(CSSPrimitiveValue::CSS_NUMBER)) + ", " + stop.m_color->cssText() + ")"; 560 } 561 } else { 562 563 result = m_repeating ? "-webkit-repeating-radial-gradient(" : "-webkit-radial-gradient("; 564 if (m_firstX && m_firstY) { 565 result += m_firstX->cssText() + " " + m_firstY->cssText(); 566 } else if (m_firstX) 567 result += m_firstX->cssText(); 568 else if (m_firstY) 569 result += m_firstY->cssText(); 570 else 571 result += "center"; 572 573 574 if (m_shape || m_sizingBehavior) { 575 result += ", "; 576 if (m_shape) 577 result += m_shape->cssText() + " "; 578 else 579 result += "ellipse "; 580 581 if (m_sizingBehavior) 582 result += m_sizingBehavior->cssText(); 583 else 584 result += "cover"; 585 586 } else if (m_endHorizontalSize && m_endVerticalSize) { 587 result += ", "; 588 result += m_endHorizontalSize->cssText() + " " + m_endVerticalSize->cssText(); 589 } 590 591 for (unsigned i = 0; i < m_stops.size(); i++) { 592 const CSSGradientColorStop& stop = m_stops[i]; 593 result += ", "; 594 result += stop.m_color->cssText(); 595 if (stop.m_position) 596 result += " " + stop.m_position->cssText(); 597 } 598 } 599 600 result += ")"; 601 return result; 602} 603 604float CSSRadialGradientValue::resolveRadius(CSSPrimitiveValue* radius, RenderStyle* style, RenderStyle* rootStyle, float* widthOrHeight) 605{ 606 float zoomFactor = style->effectiveZoom(); 607 608 float result = 0; 609 if (radius->primitiveType() == CSSPrimitiveValue::CSS_NUMBER) // Can the radius be a percentage? 610 result = radius->getFloatValue() * zoomFactor; 611 else if (widthOrHeight && radius->primitiveType() == CSSPrimitiveValue::CSS_PERCENTAGE) 612 result = *widthOrHeight * radius->getFloatValue() / 100; 613 else 614 result = radius->computeLengthFloat(style, rootStyle, zoomFactor); 615 616 return result; 617} 618 619static float distanceToClosestCorner(const FloatPoint& p, const FloatSize& size, FloatPoint& corner) 620{ 621 FloatPoint topLeft; 622 float topLeftDistance = FloatSize(p - topLeft).diagonalLength(); 623 624 FloatPoint topRight(size.width(), 0); 625 float topRightDistance = FloatSize(p - topRight).diagonalLength(); 626 627 FloatPoint bottomLeft(0, size.height()); 628 float bottomLeftDistance = FloatSize(p - bottomLeft).diagonalLength(); 629 630 FloatPoint bottomRight(size.width(), size.height()); 631 float bottomRightDistance = FloatSize(p - bottomRight).diagonalLength(); 632 633 corner = topLeft; 634 float minDistance = topLeftDistance; 635 if (topRightDistance < minDistance) { 636 minDistance = topRightDistance; 637 corner = topRight; 638 } 639 640 if (bottomLeftDistance < minDistance) { 641 minDistance = bottomLeftDistance; 642 corner = bottomLeft; 643 } 644 645 if (bottomRightDistance < minDistance) { 646 minDistance = bottomRightDistance; 647 corner = bottomRight; 648 } 649 return minDistance; 650} 651 652static float distanceToFarthestCorner(const FloatPoint& p, const FloatSize& size, FloatPoint& corner) 653{ 654 FloatPoint topLeft; 655 float topLeftDistance = FloatSize(p - topLeft).diagonalLength(); 656 657 FloatPoint topRight(size.width(), 0); 658 float topRightDistance = FloatSize(p - topRight).diagonalLength(); 659 660 FloatPoint bottomLeft(0, size.height()); 661 float bottomLeftDistance = FloatSize(p - bottomLeft).diagonalLength(); 662 663 FloatPoint bottomRight(size.width(), size.height()); 664 float bottomRightDistance = FloatSize(p - bottomRight).diagonalLength(); 665 666 corner = topLeft; 667 float maxDistance = topLeftDistance; 668 if (topRightDistance > maxDistance) { 669 maxDistance = topRightDistance; 670 corner = topRight; 671 } 672 673 if (bottomLeftDistance > maxDistance) { 674 maxDistance = bottomLeftDistance; 675 corner = bottomLeft; 676 } 677 678 if (bottomRightDistance > maxDistance) { 679 maxDistance = bottomRightDistance; 680 corner = bottomRight; 681 } 682 return maxDistance; 683} 684 685// Compute horizontal radius of ellipse with center at 0,0 which passes through p, and has 686// width/height given by aspectRatio. 687static inline float horizontalEllipseRadius(const FloatSize& p, float aspectRatio) 688{ 689 // x^2/a^2 + y^2/b^2 = 1 690 // a/b = aspectRatio, b = a/aspectRatio 691 // a = sqrt(x^2 + y^2/(1/r^2)) 692 return sqrtf(p.width() * p.width() + (p.height() * p.height()) / (1 / (aspectRatio * aspectRatio))); 693} 694 695// FIXME: share code with the linear version 696PassRefPtr<Gradient> CSSRadialGradientValue::createGradient(RenderObject* renderer, const IntSize& size) 697{ 698 ASSERT(!size.isEmpty()); 699 700 RenderStyle* rootStyle = renderer->document()->documentElement()->renderStyle(); 701 702 FloatPoint firstPoint = computeEndPoint(m_firstX.get(), m_firstY.get(), renderer->style(), rootStyle, size); 703 if (!m_firstX) 704 firstPoint.setX(size.width() / 2); 705 if (!m_firstY) 706 firstPoint.setY(size.height() / 2); 707 708 FloatPoint secondPoint = computeEndPoint(m_secondX.get(), m_secondY.get(), renderer->style(), rootStyle, size); 709 if (!m_secondX) 710 secondPoint.setX(size.width() / 2); 711 if (!m_secondY) 712 secondPoint.setY(size.height() / 2); 713 714 float firstRadius = 0; 715 if (m_firstRadius) 716 firstRadius = resolveRadius(m_firstRadius.get(), renderer->style(), rootStyle); 717 718 float secondRadius = 0; 719 float aspectRatio = 1; // width / height. 720 if (m_secondRadius) 721 secondRadius = resolveRadius(m_secondRadius.get(), renderer->style(), rootStyle); 722 else if (m_endHorizontalSize || m_endVerticalSize) { 723 float width = size.width(); 724 float height = size.height(); 725 secondRadius = resolveRadius(m_endHorizontalSize.get(), renderer->style(), rootStyle, &width); 726 aspectRatio = secondRadius / resolveRadius(m_endVerticalSize.get(), renderer->style(), rootStyle, &height); 727 } else { 728 enum GradientShape { Circle, Ellipse }; 729 GradientShape shape = Ellipse; 730 if (m_shape && m_shape->primitiveType() == CSSPrimitiveValue::CSS_IDENT && m_shape->getIdent() == CSSValueCircle) 731 shape = Circle; 732 733 enum GradientFill { ClosestSide, ClosestCorner, FarthestSide, FarthestCorner }; 734 GradientFill fill = FarthestCorner; 735 736 if (m_sizingBehavior && m_sizingBehavior->primitiveType() == CSSPrimitiveValue::CSS_IDENT) { 737 switch (m_sizingBehavior->getIdent()) { 738 case CSSValueContain: 739 case CSSValueClosestSide: 740 fill = ClosestSide; 741 break; 742 case CSSValueClosestCorner: 743 fill = ClosestCorner; 744 break; 745 case CSSValueFarthestSide: 746 fill = FarthestSide; 747 break; 748 case CSSValueCover: 749 case CSSValueFarthestCorner: 750 fill = FarthestCorner; 751 break; 752 } 753 } 754 755 // Now compute the end radii based on the second point, shape and fill. 756 757 // Horizontal 758 switch (fill) { 759 case ClosestSide: { 760 float xDist = min(secondPoint.x(), size.width() - secondPoint.x()); 761 float yDist = min(secondPoint.y(), size.height() - secondPoint.y()); 762 if (shape == Circle) { 763 float smaller = min(xDist, yDist); 764 xDist = smaller; 765 yDist = smaller; 766 } 767 secondRadius = xDist; 768 aspectRatio = xDist / yDist; 769 break; 770 } 771 case FarthestSide: { 772 float xDist = max(secondPoint.x(), size.width() - secondPoint.x()); 773 float yDist = max(secondPoint.y(), size.height() - secondPoint.y()); 774 if (shape == Circle) { 775 float larger = max(xDist, yDist); 776 xDist = larger; 777 yDist = larger; 778 } 779 secondRadius = xDist; 780 aspectRatio = xDist / yDist; 781 break; 782 } 783 case ClosestCorner: { 784 FloatPoint corner; 785 float distance = distanceToClosestCorner(secondPoint, size, corner); 786 if (shape == Circle) 787 secondRadius = distance; 788 else { 789 // If <shape> is ellipse, the gradient-shape has the same ratio of width to height 790 // that it would if closest-side or farthest-side were specified, as appropriate. 791 float xDist = min(secondPoint.x(), size.width() - secondPoint.x()); 792 float yDist = min(secondPoint.y(), size.height() - secondPoint.y()); 793 794 secondRadius = horizontalEllipseRadius(corner - secondPoint, xDist / yDist); 795 aspectRatio = xDist / yDist; 796 } 797 break; 798 } 799 800 case FarthestCorner: { 801 FloatPoint corner; 802 float distance = distanceToFarthestCorner(secondPoint, size, corner); 803 if (shape == Circle) 804 secondRadius = distance; 805 else { 806 // If <shape> is ellipse, the gradient-shape has the same ratio of width to height 807 // that it would if closest-side or farthest-side were specified, as appropriate. 808 float xDist = max(secondPoint.x(), size.width() - secondPoint.x()); 809 float yDist = max(secondPoint.y(), size.height() - secondPoint.y()); 810 811 secondRadius = horizontalEllipseRadius(corner - secondPoint, xDist / yDist); 812 aspectRatio = xDist / yDist; 813 } 814 break; 815 } 816 } 817 } 818 819 RefPtr<Gradient> gradient = Gradient::create(firstPoint, firstRadius, secondPoint, secondRadius, aspectRatio); 820 821 // addStops() only uses maxExtent for repeating gradients. 822 float maxExtent = 0; 823 if (m_repeating) { 824 FloatPoint corner; 825 maxExtent = distanceToFarthestCorner(secondPoint, size, corner); 826 } 827 828 // Now add the stops. 829 addStops(gradient.get(), renderer, rootStyle, maxExtent); 830 831 return gradient.release(); 832} 833 834} // namespace WebCore 835