Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(141)

Side by Side Diff: third_party/WebKit/Source/core/css/CSSGradientValue.cpp

Issue 2792163002: Add support for repeating-conic-gradient() (Closed)
Patch Set: simplified impl Created 3 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
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
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
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
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
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
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
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698