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()) { |
| 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 |