Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 /* | 1 /* |
| 2 * Copyright (C) 2008 Apple Inc. All rights reserved. | 2 * Copyright (C) 2008 Apple Inc. All rights reserved. |
| 3 * Copyright (C) 2015 Google Inc. All rights reserved. | 3 * Copyright (C) 2015 Google Inc. All rights reserved. |
| 4 * | 4 * |
| 5 * Redistribution and use in source and binary forms, with or without | 5 * Redistribution and use in source and binary forms, with or without |
| 6 * modification, are permitted provided that the following conditions | 6 * modification, are permitted provided that the following conditions |
| 7 * are met: | 7 * are met: |
| 8 * 1. Redistributions of source code must retain the above copyright | 8 * 1. Redistributions of source code must retain the above copyright |
| 9 * notice, this list of conditions and the following disclaimer. | 9 * notice, this list of conditions and the following disclaimer. |
| 10 * 2. Redistributions in binary form must reproduce the above copyright | 10 * 2. Redistributions in binary form must reproduce the above copyright |
| (...skipping 291 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 302 float offset; | 302 float offset; |
| 303 if (stop.m_offset->isPercentage()) | 303 if (stop.m_offset->isPercentage()) |
| 304 offset = stop.m_offset->getFloatValue() / 100; | 304 offset = stop.m_offset->getFloatValue() / 100; |
| 305 else | 305 else |
| 306 offset = stop.m_offset->getFloatValue(); | 306 offset = stop.m_offset->getFloatValue(); |
| 307 | 307 |
| 308 desc.stops.emplace_back(offset, resolveStopColor(*stop.m_color, object)); | 308 desc.stops.emplace_back(offset, resolveStopColor(*stop.m_color, object)); |
| 309 } | 309 } |
| 310 } | 310 } |
| 311 | 311 |
| 312 static bool requiresStopsNormalization(const Vector<GradientStop>& stops, | 312 namespace { |
| 313 CSSGradientValue::GradientDesc& desc) { | 313 |
| 314 bool requiresStopsNormalization(const Vector<GradientStop>& stops, | |
| 315 CSSGradientValue::GradientDesc& desc) { | |
| 314 // We need at least two stops to normalize | 316 // We need at least two stops to normalize |
| 315 if (stops.size() < 2) | 317 if (stops.size() < 2) |
| 316 return false; | 318 return false; |
| 317 | 319 |
| 318 // Repeating gradients are implemented using a normalized stop offset range | 320 // Repeating gradients are implemented using a normalized stop offset range |
| 319 // with the point/radius pairs aligned on the interval endpoints. | 321 // with the point/radius pairs aligned on the interval endpoints. |
| 320 if (desc.spreadMethod == SpreadMethodRepeat) | 322 if (desc.spreadMethod == SpreadMethodRepeat) |
| 321 return true; | 323 return true; |
| 322 | 324 |
| 323 // Degenerate stops | 325 // Degenerate stops |
| 324 if (stops.front().offset < 0 || stops.back().offset > 1) | 326 if (stops.front().offset < 0 || stops.back().offset > 1) |
| 325 return true; | 327 return true; |
| 326 | 328 |
| 327 return false; | 329 return false; |
| 328 } | 330 } |
| 329 | 331 |
| 330 // Redistribute the stops such that they fully cover [0 , 1] and add them to the | 332 // Redistribute the stops such that they fully cover [0 , 1] and add them to the |
| 331 // gradient. | 333 // gradient. |
| 332 static bool normalizeAndAddStops(const Vector<GradientStop>& stops, | 334 bool normalizeAndAddStops(const Vector<GradientStop>& stops, |
| 333 CSSGradientValue::GradientDesc& desc) { | 335 CSSGradientValue::GradientDesc& desc) { |
| 334 DCHECK_GT(stops.size(), 1u); | 336 DCHECK_GT(stops.size(), 1u); |
| 335 | 337 |
| 336 const float firstOffset = stops.front().offset; | 338 const float firstOffset = stops.front().offset; |
| 337 const float lastOffset = stops.back().offset; | 339 const float lastOffset = stops.back().offset; |
| 338 const float span = lastOffset - firstOffset; | 340 const float span = lastOffset - firstOffset; |
| 339 | 341 |
| 340 if (fabs(span) < std::numeric_limits<float>::epsilon()) { | 342 if (fabs(span) < std::numeric_limits<float>::epsilon()) { |
| 341 // All stops are coincident -> use a single clamped offset value. | 343 // All stops are coincident -> use a single clamped offset value. |
| 342 const float clampedOffset = std::min(std::max(firstOffset, 0.f), 1.f); | 344 const float clampedOffset = std::min(std::max(firstOffset, 0.f), 1.f); |
| 343 | 345 |
| (...skipping 20 matching lines...) Expand all Loading... | |
| 364 normalizedOffset >= (stops[i - 1].offset - firstOffset) / span); | 366 normalizedOffset >= (stops[i - 1].offset - firstOffset) / span); |
| 365 | 367 |
| 366 desc.stops.emplace_back(normalizedOffset, stops[i].color); | 368 desc.stops.emplace_back(normalizedOffset, stops[i].color); |
| 367 } | 369 } |
| 368 | 370 |
| 369 return true; | 371 return true; |
| 370 } | 372 } |
| 371 | 373 |
| 372 // Collapse all negative-offset stops to 0 and compute an interpolated color | 374 // Collapse all negative-offset stops to 0 and compute an interpolated color |
| 373 // value for that point. | 375 // value for that point. |
| 374 static void clampNegativeOffsets(Vector<GradientStop>& stops) { | 376 void clampNegativeOffsets(Vector<GradientStop>& stops) { |
| 375 float lastNegativeOffset = 0; | 377 float lastNegativeOffset = 0; |
| 376 | 378 |
| 377 for (size_t i = 0; i < stops.size(); ++i) { | 379 for (size_t i = 0; i < stops.size(); ++i) { |
| 378 const float currentOffset = stops[i].offset; | 380 const float currentOffset = stops[i].offset; |
| 379 if (currentOffset >= 0) { | 381 if (currentOffset >= 0) { |
| 380 if (i > 0) { | 382 if (i > 0) { |
| 381 // We found the negative -> positive offset transition: compute an | 383 // We found the negative -> positive offset transition: compute an |
| 382 // interpolated color value for 0 and use it with the last clamped stop. | 384 // interpolated color value for 0 and use it with the last clamped stop. |
| 383 DCHECK_LT(lastNegativeOffset, 0); | 385 DCHECK_LT(lastNegativeOffset, 0); |
| 384 float lerpRatio = | 386 float lerpRatio = |
| 385 -lastNegativeOffset / (currentOffset - lastNegativeOffset); | 387 -lastNegativeOffset / (currentOffset - lastNegativeOffset); |
| 386 stops[i - 1].color = | 388 stops[i - 1].color = |
| 387 blend(stops[i - 1].color, stops[i].color, lerpRatio); | 389 blend(stops[i - 1].color, stops[i].color, lerpRatio); |
| 388 } | 390 } |
| 389 | 391 |
| 390 break; | 392 break; |
| 391 } | 393 } |
| 392 | 394 |
| 393 // Clamp all negative stops to 0. | 395 // Clamp all negative stops to 0. |
| 394 stops[i].offset = 0; | 396 stops[i].offset = 0; |
| 395 lastNegativeOffset = currentOffset; | 397 lastNegativeOffset = currentOffset; |
| 396 } | 398 } |
| 397 } | 399 } |
| 398 | 400 |
| 399 // Update the linear gradient points to align with the given offset range. | 401 // Update the linear gradient points to align with the given offset range. |
| 400 static void adjustGradientPointsForOffsetRange( | 402 void adjustGradientPointsForOffsetRange(CSSGradientValue::GradientDesc& desc, |
| 401 CSSGradientValue::GradientDesc& desc, | 403 float firstOffset, |
| 402 float firstOffset, | 404 float lastOffset) { |
| 403 float lastOffset) { | |
| 404 DCHECK_LE(firstOffset, lastOffset); | 405 DCHECK_LE(firstOffset, lastOffset); |
| 405 | 406 |
| 406 const FloatPoint p0 = desc.p0; | 407 const FloatPoint p0 = desc.p0; |
| 407 const FloatPoint p1 = desc.p1; | 408 const FloatPoint p1 = desc.p1; |
| 408 const FloatSize d(p1 - p0); | 409 const FloatSize d(p1 - p0); |
| 409 | 410 |
| 410 // Linear offsets are relative to the [p0 , p1] segment. | 411 // Linear offsets are relative to the [p0 , p1] segment. |
| 411 desc.p0 = p0 + d * firstOffset; | 412 desc.p0 = p0 + d * firstOffset; |
| 412 desc.p1 = p0 + d * lastOffset; | 413 desc.p1 = p0 + d * lastOffset; |
| 413 } | 414 } |
| 414 | 415 |
| 415 // Update the radial gradient radii to align with the given offset range. | 416 // Update the radial gradient radii to align with the given offset range. |
| 416 static void adjustGradientRadiiForOffsetRange( | 417 void adjustGradientRadiiForOffsetRange(CSSGradientValue::GradientDesc& desc, |
| 417 CSSGradientValue::GradientDesc& desc, | 418 float firstOffset, |
| 418 float firstOffset, | 419 float lastOffset) { |
| 419 float lastOffset) { | |
| 420 DCHECK_LE(firstOffset, lastOffset); | 420 DCHECK_LE(firstOffset, lastOffset); |
| 421 | 421 |
| 422 // Radial offsets are relative to the [0 , endRadius] segment. | 422 // Radial offsets are relative to the [0 , endRadius] segment. |
| 423 float adjustedR0 = desc.r1 * firstOffset; | 423 float adjustedR0 = desc.r1 * firstOffset; |
| 424 float adjustedR1 = desc.r1 * lastOffset; | 424 float adjustedR1 = desc.r1 * lastOffset; |
| 425 DCHECK_LE(adjustedR0, adjustedR1); | 425 DCHECK_LE(adjustedR0, adjustedR1); |
| 426 // Unlike linear gradients (where we can adjust the points arbitrarily), | 426 // Unlike linear gradients (where we can adjust the points arbitrarily), |
| 427 // we cannot let our radii turn negative here. | 427 // we cannot let our radii turn negative here. |
| 428 if (adjustedR0 < 0) { | 428 if (adjustedR0 < 0) { |
| 429 // For the non-repeat case, this can never happen: clampNegativeOffsets() | 429 // For the non-repeat case, this can never happen: clampNegativeOffsets() |
| (...skipping 10 matching lines...) Expand all Loading... | |
| 440 adjustedR0 += shiftToPositive; | 440 adjustedR0 += shiftToPositive; |
| 441 adjustedR1 += shiftToPositive; | 441 adjustedR1 += shiftToPositive; |
| 442 } | 442 } |
| 443 DCHECK_GE(adjustedR0, 0); | 443 DCHECK_GE(adjustedR0, 0); |
| 444 DCHECK_GE(adjustedR1, adjustedR0); | 444 DCHECK_GE(adjustedR1, adjustedR0); |
| 445 | 445 |
| 446 desc.r0 = adjustedR0; | 446 desc.r0 = adjustedR0; |
| 447 desc.r1 = adjustedR1; | 447 desc.r1 = adjustedR1; |
| 448 } | 448 } |
| 449 | 449 |
| 450 // Helper for performing on-the-fly out of range color stop interpolation. | |
| 451 class ConicClamper { | |
| 452 STACK_ALLOCATED(); | |
| 453 | |
| 454 public: | |
| 455 ConicClamper(CSSGradientValue::GradientDesc& desc) : m_desc(desc) {} | |
| 456 | |
| 457 void add(float offset, const Color& color) { | |
| 458 addInternal(offset, color); | |
| 459 m_prevOffset = offset; | |
| 460 m_prevColor = color; | |
| 461 } | |
| 462 | |
| 463 private: | |
| 464 void addUnique(float offset, const Color& color) { | |
| 465 // Skip duplicates. | |
| 466 if (m_desc.stops.isEmpty() || offset != m_desc.stops.back().stop || | |
| 467 color != m_desc.stops.back().color) { | |
| 468 m_desc.stops.emplace_back(offset, color); | |
| 469 } | |
| 470 } | |
| 471 | |
| 472 void addInternal(float offset, const Color& color) { | |
| 473 if (offset < 0) | |
| 474 return; | |
| 475 | |
| 476 if (m_prevOffset < 0 && offset > 0) { | |
| 477 addUnique(0, blend(m_prevColor, color, | |
| 478 -m_prevOffset / (offset - m_prevOffset))); | |
| 479 } | |
| 480 | |
| 481 if (offset <= 1) { | |
| 482 addUnique(offset, color); | |
| 483 return; | |
| 484 } | |
| 485 | |
| 486 if (m_prevOffset < 1) { | |
| 487 addUnique(1, blend(m_prevColor, color, | |
| 488 (1 - m_prevOffset) / (offset - m_prevOffset))); | |
| 489 } | |
| 490 } | |
| 491 | |
| 492 CSSGradientValue::GradientDesc& m_desc; | |
| 493 | |
| 494 float m_prevOffset = 0; | |
| 495 Color m_prevColor; | |
| 496 }; | |
| 497 | |
| 498 void normalizeAndAddConicStops(const Vector<GradientStop>& stops, | |
| 499 CSSGradientValue::GradientDesc& desc) { | |
| 500 DCHECK(!stops.isEmpty()); | |
| 501 ConicClamper clamper(desc); | |
| 502 | |
| 503 if (desc.spreadMethod == SpreadMethodPad) { | |
| 504 for (const auto& stop : stops) | |
| 505 clamper.add(stop.offset, stop.color); | |
| 506 return; | |
| 507 } | |
| 508 | |
| 509 DCHECK_EQ(desc.spreadMethod, SpreadMethodRepeat); | |
| 510 | |
| 511 // The normalization trick we use for linear and radial doesn't work here, | |
| 512 // because the underlying Skia implementation doesn't support conic gradient | |
| 513 // tiling. So we emit synthetic stops to cover the whole unit interval. | |
| 514 float repeatSpan = stops.back().offset - stops.front().offset; | |
| 515 DCHECK_GE(repeatSpan, 0.0f); | |
| 516 if (repeatSpan < std::numeric_limits<float>::epsilon()) { | |
|
fs
2017/04/05 19:19:57
Should we be worried about small (greater than eps
f(malita)
2017/04/05 19:33:57
Yup, that's not ideal. The current Skia impl is u
fs
2017/04/05 20:04:11
We're good then it sounds like.
| |
| 517 // All stops are coincident -> use a single solid color. | |
| 518 desc.stops.emplace_back(0, stops.back().color); | |
| 519 return; | |
| 520 } | |
| 521 | |
| 522 // Compute an offset base as a repetition of stops[0].offset such that | |
| 523 // [ offsetBase, offsetBase + repeatSpan ] contains 0 | |
| 524 // (aka the largest repeat value for stops[0] less than 0). | |
| 525 float offset = fmodf(stops.front().offset, repeatSpan) - | |
| 526 (stops.front().offset < 0 ? 0 : repeatSpan); | |
| 527 DCHECK_LE(offset, 0); | |
| 528 DCHECK_GE(offset + repeatSpan, 0); | |
| 529 | |
| 530 // Start throwing repeating values at the clamper. | |
| 531 do { | |
| 532 const float offsetBase = offset; | |
| 533 for (const auto& stop : stops) { | |
| 534 offset = offsetBase + stop.offset - stops.front().offset; | |
| 535 clamper.add(offset, stop.color); | |
| 536 | |
| 537 if (offset >= 1) | |
| 538 break; | |
| 539 } | |
| 540 } while (offset < 1); | |
| 541 } | |
| 542 | |
| 543 } // anonymous ns | |
| 544 | |
| 450 void CSSGradientValue::addStops(CSSGradientValue::GradientDesc& desc, | 545 void CSSGradientValue::addStops(CSSGradientValue::GradientDesc& desc, |
| 451 const CSSToLengthConversionData& conversionData, | 546 const CSSToLengthConversionData& conversionData, |
| 452 const LayoutObject& object) { | 547 const LayoutObject& object) { |
| 453 if (m_gradientType == CSSDeprecatedLinearGradient || | 548 if (m_gradientType == CSSDeprecatedLinearGradient || |
| 454 m_gradientType == CSSDeprecatedRadialGradient) { | 549 m_gradientType == CSSDeprecatedRadialGradient) { |
| 455 addDeprecatedStops(desc, object); | 550 addDeprecatedStops(desc, object); |
| 456 return; | 551 return; |
| 457 } | 552 } |
| 458 | 553 |
| 459 size_t numStops = m_stops.size(); | 554 size_t numStops = m_stops.size(); |
| (...skipping 108 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 568 } | 663 } |
| 569 } | 664 } |
| 570 | 665 |
| 571 DCHECK_EQ(stops.size(), m_stops.size()); | 666 DCHECK_EQ(stops.size(), m_stops.size()); |
| 572 if (hasHints) { | 667 if (hasHints) { |
| 573 replaceColorHintsWithColorStops(stops, m_stops); | 668 replaceColorHintsWithColorStops(stops, m_stops); |
| 574 } | 669 } |
| 575 | 670 |
| 576 // At this point we have a fully resolved set of stops. Time to perform | 671 // At this point we have a fully resolved set of stops. Time to perform |
| 577 // adjustments for repeat gradients and degenerate values if needed. | 672 // adjustments for repeat gradients and degenerate values if needed. |
| 578 // Note: the normalization trick doesn't work for conic gradients, because | 673 if (!requiresStopsNormalization(stops, desc)) { |
| 579 // the underlying Skia implementation doesn't support tiling. | |
| 580 if (isConicGradientValue() || !requiresStopsNormalization(stops, desc)) { | |
| 581 // No normalization required, just add the current stops. | 674 // No normalization required, just add the current stops. |
| 582 for (const auto& stop : stops) | 675 for (const auto& stop : stops) |
| 583 desc.stops.emplace_back(stop.offset, stop.color); | 676 desc.stops.emplace_back(stop.offset, stop.color); |
| 584 return; | 677 return; |
| 585 } | 678 } |
| 586 | 679 |
| 587 switch (getClassType()) { | 680 switch (getClassType()) { |
| 588 case LinearGradientClass: | 681 case LinearGradientClass: |
| 589 if (normalizeAndAddStops(stops, desc)) { | 682 if (normalizeAndAddStops(stops, desc)) { |
| 590 adjustGradientPointsForOffsetRange(desc, stops.front().offset, | 683 adjustGradientPointsForOffsetRange(desc, stops.front().offset, |
| 591 stops.back().offset); | 684 stops.back().offset); |
| 592 } | 685 } |
| 593 break; | 686 break; |
| 594 case RadialGradientClass: | 687 case RadialGradientClass: |
| 595 // Negative offsets are only an issue for non-repeating radial gradients: | 688 // Negative offsets are only an issue for non-repeating radial gradients: |
| 596 // linear gradient points can be repositioned arbitrarily, and for | 689 // linear gradient points can be repositioned arbitrarily, and for |
| 597 // repeating radial gradients we shift the radii into equivalent positive | 690 // repeating radial gradients we shift the radii into equivalent positive |
| 598 // values. | 691 // values. |
| 599 if (!m_repeating) | 692 if (!m_repeating) |
| 600 clampNegativeOffsets(stops); | 693 clampNegativeOffsets(stops); |
| 601 | 694 |
| 602 if (normalizeAndAddStops(stops, desc)) { | 695 if (normalizeAndAddStops(stops, desc)) { |
| 603 adjustGradientRadiiForOffsetRange(desc, stops.front().offset, | 696 adjustGradientRadiiForOffsetRange(desc, stops.front().offset, |
| 604 stops.back().offset); | 697 stops.back().offset); |
| 605 } | 698 } |
| 606 break; | 699 break; |
| 700 case ConicGradientClass: | |
| 701 normalizeAndAddConicStops(stops, desc); | |
| 702 break; | |
| 607 default: | 703 default: |
| 608 NOTREACHED(); | 704 NOTREACHED(); |
| 609 } | 705 } |
| 610 } | 706 } |
| 611 | 707 |
| 612 static float positionFromValue(const CSSValue* value, | 708 static float positionFromValue(const CSSValue* value, |
| 613 const CSSToLengthConversionData& conversionData, | 709 const CSSToLengthConversionData& conversionData, |
| 614 const IntSize& size, | 710 const IntSize& size, |
| 615 bool isHorizontal) { | 711 bool isHorizontal) { |
| 616 int origin = 0; | 712 int origin = 0; |
| (...skipping 774 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1391 const FloatPoint position( | 1487 const FloatPoint position( |
| 1392 m_firstX ? positionFromValue(m_firstX, conversionData, size, true) | 1488 m_firstX ? positionFromValue(m_firstX, conversionData, size, true) |
| 1393 : size.width() / 2, | 1489 : size.width() / 2, |
| 1394 m_firstY ? positionFromValue(m_firstY, conversionData, size, false) | 1490 m_firstY ? positionFromValue(m_firstY, conversionData, size, false) |
| 1395 : size.height() / 2); | 1491 : size.height() / 2); |
| 1396 | 1492 |
| 1397 GradientDesc desc(position, position, | 1493 GradientDesc desc(position, position, |
| 1398 m_repeating ? SpreadMethodRepeat : SpreadMethodPad); | 1494 m_repeating ? SpreadMethodRepeat : SpreadMethodPad); |
| 1399 addStops(desc, conversionData, object); | 1495 addStops(desc, conversionData, object); |
| 1400 | 1496 |
| 1401 RefPtr<Gradient> gradient = | 1497 RefPtr<Gradient> gradient = Gradient::createConic( |
| 1402 Gradient::createConic(position, angle, desc.spreadMethod, | 1498 position, angle, Gradient::ColorInterpolation::Premultiplied); |
| 1403 Gradient::ColorInterpolation::Premultiplied); | |
| 1404 gradient->addColorStops(desc.stops); | 1499 gradient->addColorStops(desc.stops); |
| 1405 | 1500 |
| 1406 return gradient.release(); | 1501 return gradient.release(); |
| 1407 } | 1502 } |
| 1408 | 1503 |
| 1409 bool CSSConicGradientValue::equals(const CSSConicGradientValue& other) const { | 1504 bool CSSConicGradientValue::equals(const CSSConicGradientValue& other) const { |
| 1410 return m_repeating == other.m_repeating && | 1505 return m_repeating == other.m_repeating && |
| 1411 dataEquivalent(m_firstX, other.m_firstX) && | 1506 dataEquivalent(m_firstX, other.m_firstX) && |
| 1412 dataEquivalent(m_firstY, other.m_firstY) && | 1507 dataEquivalent(m_firstY, other.m_firstY) && |
| 1413 dataEquivalent(m_fromAngle, other.m_fromAngle) && | 1508 dataEquivalent(m_fromAngle, other.m_fromAngle) && |
| 1414 m_stops == other.m_stops; | 1509 m_stops == other.m_stops; |
| 1415 } | 1510 } |
| 1416 | 1511 |
| 1417 DEFINE_TRACE_AFTER_DISPATCH(CSSConicGradientValue) { | 1512 DEFINE_TRACE_AFTER_DISPATCH(CSSConicGradientValue) { |
| 1418 visitor->trace(m_fromAngle); | 1513 visitor->trace(m_fromAngle); |
| 1419 CSSGradientValue::traceAfterDispatch(visitor); | 1514 CSSGradientValue::traceAfterDispatch(visitor); |
| 1420 } | 1515 } |
| 1421 | 1516 |
| 1422 } // namespace blink | 1517 } // namespace blink |
| OLD | NEW |