Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions src/Renderer/Color/Cmyk.php
Original file line number Diff line number Diff line change
Expand Up @@ -58,15 +58,15 @@ public function getBlack() : int

public function toRgb() : Rgb
{
$c = $this->cyan / 100;
$m = $this->magenta / 100;
$y = $this->yellow / 100;
$k = $this->black / 100;
$c = (-$k * $this->cyan + $k * 100 + $this->cyan) / 100;
$m = (-$k * $this->magenta + $k * 100 + $this->magenta) / 100;
$y = (-$k * $this->yellow + $k * 100 + $this->yellow) / 100;

return new Rgb(
(int) (-$c * 255 + 255),
(int) (-$m * 255 + 255),
(int) (-$y * 255 + 255)
(int) round(255 * (1 - $c) * (1 - $k)),
(int) round(255 * (1 - $m) * (1 - $k)),
(int) round(255 * (1 - $y) * (1 - $k))
);
}

Expand Down
5 changes: 4 additions & 1 deletion src/Renderer/Color/Gray.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,10 @@ public function getGray() : int

public function toRgb() : Rgb
{
return new Rgb((int) ($this->gray * 2.55), (int) ($this->gray * 2.55), (int) ($this->gray * 2.55));
// use 255/100 instead of 2.55 to avoid floating-point precision loss (100 * 2.55 = 254.999...)
$value = (int) round($this->gray * 255 / 100);

return new Rgb($value, $value, $value);
}

public function toCmyk() : Cmyk
Expand Down
20 changes: 11 additions & 9 deletions src/Renderer/Color/Rgb.php
Original file line number Diff line number Diff line change
Expand Up @@ -49,25 +49,27 @@ public function toRgb() : Rgb

public function toCmyk() : Cmyk
{
// avoid division by zero with input rgb(0,0,0), by handling it as a specific case
if (0 === $this->red && 0 === $this->green && 0 === $this->blue) {
return new Cmyk(0, 0, 0, 100);
}

$c = 1 - ($this->red / 255);
$m = 1 - ($this->green / 255);
$y = 1 - ($this->blue / 255);
$k = min($c, $m, $y);

if ($k === 0) {
return new Cmyk(0, 0, 0, 0);
}

return new Cmyk(
(int) (100 * ($c - $k) / (1 - $k)),
(int) (100 * ($m - $k) / (1 - $k)),
(int) (100 * ($y - $k) / (1 - $k)),
(int) (100 * $k)
(int) round(100 * ($c - $k) / (1 - $k)),
(int) round(100 * ($m - $k) / (1 - $k)),
(int) round(100 * ($y - $k) / (1 - $k)),
(int) round(100 * $k)
);
}

public function toGray() : Gray
{
return new Gray((int) (($this->red * 0.21 + $this->green * 0.71 + $this->blue * 0.07) / 2.55));
// use integer-based calculation to avoid floating-point precision loss
return new Gray((int) round(($this->red * 2126 + $this->green * 7152 + $this->blue * 722) / 25500));
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="400" height="400" viewBox="0 0 400 400">
<rect x="0" y="0" width="400" height="400" fill="#fefefe"/>
<rect x="0" y="0" width="400" height="400" fill="#ffffff"/>
<g transform="scale(13.793)">
<g transform="translate(4.000,4.000)">
<path fill-rule="evenodd" d="M8 0L8 1L9 1L9 2L8 2L8 5L11 5L11 4L12 4L12 3L13 3L13 0L12 0L12 3L11 3L11 2L10 2L10 0ZM9 2L9 4L11 4L11 3L10 3L10 2ZM8 6L8 7L9 7L9 8L8 8L8 9L7 9L7 8L6 8L6 9L7 9L7 10L5 10L5 9L4 9L4 8L0 8L0 10L2 10L2 11L0 11L0 13L2 13L2 11L3 11L3 13L8 13L8 14L10 14L10 15L9 15L9 17L8 17L8 21L9 21L9 19L11 19L11 21L12 21L12 19L13 19L13 21L16 21L16 20L17 20L17 19L16 19L16 18L15 18L15 17L14 17L14 18L15 18L15 19L13 19L13 17L12 17L12 16L14 16L14 15L15 15L15 16L16 16L16 15L17 15L17 18L18 18L18 19L19 19L19 18L20 18L20 16L19 16L19 15L17 15L17 14L16 14L16 13L15 13L15 12L16 12L16 10L19 10L19 12L20 12L20 14L21 14L21 12L20 12L20 11L21 11L21 8L20 8L20 10L19 10L19 8L16 8L16 9L14 9L14 8L13 8L13 9L12 9L12 12L10 12L10 9L11 9L11 6L10 6L10 7L9 7L9 6ZM12 6L12 7L13 7L13 6ZM9 8L9 9L8 9L8 10L7 10L7 11L5 11L5 10L3 10L3 11L4 11L4 12L7 12L7 11L9 11L9 9L10 9L10 8ZM14 10L14 11L13 11L13 12L12 12L12 14L11 14L11 15L13 15L13 14L14 14L14 13L13 13L13 12L15 12L15 10ZM17 11L17 12L18 12L18 11ZM8 12L8 13L9 13L9 12ZM18 13L18 14L19 14L19 13ZM10 16L10 17L9 17L9 18L10 18L10 17L11 17L11 19L12 19L12 17L11 17L11 16ZM20 19L20 20L21 20L21 19ZM18 20L18 21L19 21L19 20ZM0 0L0 7L7 7L7 0ZM1 1L1 6L6 6L6 1ZM2 2L2 5L5 5L5 2ZM21 0L14 0L14 7L21 7ZM20 1L15 1L15 6L20 6ZM19 2L16 2L16 5L19 5ZM0 21L7 21L7 14L0 14ZM1 20L6 20L6 15L1 15ZM2 19L5 19L5 16L2 16Z" fill="#000000"/>
Expand Down
62 changes: 62 additions & 0 deletions test/Renderer/Color/CmykTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<?php
declare(strict_types = 1);

namespace BaconQrCodeTest\Renderer\Color;

use BaconQrCode\Renderer\Color\Cmyk;
use BaconQrCode\Renderer\Color\Gray;
use BaconQrCode\Renderer\Color\Rgb;
use PHPUnit\Framework\TestCase;

final class CmykTest extends TestCase
{
/**
* Tests CMYK to RGB conversion, focusing on correct application of rounding.
*/
public function testToRgb() : void
{
// Pure Black (C:0, M:0, Y:0, K:100) -> RGB(0, 0, 0)
$cmykBlack = new Cmyk(0, 0, 0, 100);
$this->assertEquals(new Rgb(0, 0, 0), $cmykBlack->toRgb(), 'CMYK Black to RGB');

// Pure White (C:0, M:0, Y:0, K:0) -> RGB(255, 255, 255)
$cmykWhite = new Cmyk(0, 0, 0, 0);
$this->assertEquals(new Rgb(255, 255, 255), $cmykWhite->toRgb(), 'CMYK White to RGB');

// Mid Gray (C:0, M:0, Y:0, K:50) -> RGB(128, 128, 128)
// Check for rounding: 255 * (1 - 0) * (1 - 0.5) = 127.5 -> round(127.5) = 128
$cmykGray = new Cmyk(0, 0, 0, 50);
$this->assertEquals(new Rgb(128, 128, 128), $cmykGray->toRgb(), 'CMYK Gray to RGB (rounding check)');

// Complex Color (Dark Red): C:10, M:80, Y:70, K:30
// R: round(255 * 0.9 * 0.7) = round(160.65) = 161
// G: round(255 * 0.2 * 0.7) = round(35.7) = 36
// B: round(255 * 0.3 * 0.7) = round(53.55) = 54
$cmykColor = new Cmyk(10, 80, 70, 30);
$this->assertEquals(new Rgb(161, 36, 54), $cmykColor->toRgb(), 'CMYK Complex Color to RGB');
}

public function testToCmyk() : void
{
$cmyk = new Cmyk(10, 20, 30, 40);
$this->assertSame($cmyk, $cmyk->toCmyk(), 'toCmyk should return $this');
}

/**
* Tests CMYK to Gray conversion via RGB.
*/
public function testToGray() : void
{
// White (K:0) -> Gray(100)
$cmykWhite = new Cmyk(0, 0, 0, 0);
$this->assertEquals(new Gray(100), $cmykWhite->toGray(), 'CMYK White to Gray');

// Black (K:100) -> Gray(0)
$cmykBlack = new Cmyk(0, 0, 0, 100);
$this->assertEquals(new Gray(0), $cmykBlack->toGray(), 'CMYK Black to Gray');

// Pure Gray (K:50) -> Should result in Gray(50)
$cmykGray = new Cmyk(0, 0, 0, 50);
$this->assertEquals(new Gray(50), $cmykGray->toGray(), 'CMYK Gray to Gray');
}
}
63 changes: 63 additions & 0 deletions test/Renderer/Color/GrayTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<?php
declare(strict_types = 1);

namespace BaconQrCodeTest\Renderer\Color;

use BaconQrCode\Renderer\Color\Cmyk;
use BaconQrCode\Renderer\Color\Gray;
use BaconQrCode\Renderer\Color\Rgb;
use PHPUnit\Framework\TestCase;

final class GrayTest extends TestCase
{
/**
* Tests Gray to RGB conversion, focusing on:
* 1. Using 255/100 instead of 2.55 to avoid floating-point precision loss.
* 2. Correct application of rounding.
*/
public function testToRgb() : void
{
// Black (0) -> RGB(0, 0, 0)
$grayBlack = new Gray(0);
$this->assertEquals(new Rgb(0, 0, 0), $grayBlack->toRgb(), 'Gray Black to RGB');

// White (100) -> RGB(255, 255, 255)
// 100 * 255 / 100 = 255
$grayWhite = new Gray(100);
$this->assertEquals(new Rgb(255, 255, 255), $grayWhite->toRgb(), 'Gray White to RGB');

// Midpoint (50) -> RGB(128, 128, 128)
// Check for precision and rounding: 50 * 255 / 100 = 127.5 -> round(127.5) = 128
$grayMiddle = new Gray(50);
$this->assertEquals(new Rgb(128, 128, 128), $grayMiddle->toRgb(), 'Gray 50 to RGB (rounding check)');

// Custom value (90) -> RGB(230, 230, 230)
// Check for precision and rounding: 90 * 255 / 100 = 229.5 -> round(229.5) = 230
$grayCustom = new Gray(90);
$this->assertEquals(new Rgb(230, 230, 230), $grayCustom->toRgb(), 'Gray Custom to RGB (rounding check)');
}

/**
* Tests Gray to CMYK conversion (K=100-Gray).
*/
public function testToCmyk() : void
{
// Black (0) -> K:100
$grayBlack = new Gray(0);
$this->assertEquals(new Cmyk(0, 0, 0, 100), $grayBlack->toCmyk(), 'Gray Black to CMYK');

// White (100) -> K:0
$grayWhite = new Gray(100);
$this->assertEquals(new Cmyk(0, 0, 0, 0), $grayWhite->toCmyk(), 'Gray White to CMYK');

// Middle (50) -> K:50
$grayMiddle = new Gray(50);
$this->assertEquals(new Cmyk(0, 0, 0, 50), $grayMiddle->toCmyk(), 'Gray Middle to CMYK');
}

public function testToGray() : void
{
$gray = new Gray(75);
$this->assertSame($gray, $gray->toGray(), 'toGray should return $this');
}
}
78 changes: 78 additions & 0 deletions test/Renderer/Color/RgbTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
<?php
declare(strict_types = 1);

namespace BaconQrCodeTest\Renderer\Color;

use BaconQrCode\Renderer\Color\Cmyk;
use BaconQrCode\Renderer\Color\Gray;
use BaconQrCode\Renderer\Color\Rgb;
use PHPUnit\Framework\TestCase;

final class RgbTest extends TestCase
{
public function testToRgb() : void
{
$rgb = new Rgb(10, 20, 30);
$this->assertSame($rgb, $rgb->toRgb(), 'toRgb should return $this');
}

/**
* Tests RGB to CMYK conversion, focusing on:
* 1. Handling the special case RGB(0, 0, 0) -> CMYK(0, 0, 0, 100).
* 2. Correct application of rounding.
*/
public function testToCmyk() : void
{
// Special Case: Black (0, 0, 0) -> C:0, M:0, Y:0, K:100
$rgbBlack = new Rgb(0, 0, 0);
$this->assertEquals(new Cmyk(0, 0, 0, 100), $rgbBlack->toCmyk(), 'RGB Black to CMYK (special case)');

// White (255, 255, 255) -> C:0, M:0, Y:0, K:0
$rgbWhite = new Rgb(255, 255, 255);
$this->assertEquals(new Cmyk(0, 0, 0, 0), $rgbWhite->toCmyk(), 'RGB White to CMYK');

// Pure Red (255, 0, 0) -> C:0, M:100, Y:100, K:0
$rgbRed = new Rgb(255, 0, 0);
$this->assertEquals(new Cmyk(0, 100, 100, 0), $rgbRed->toCmyk(), 'RGB Red to CMYK');

// Complex Color checking rounding: RGB(100, 150, 200)
// K=22 (round(100 * 0.2156))
// C=50 (round(100 * 0.3922 / 0.7844))
// M=25 (round(100 * 0.1961 / 0.7844))
// Y=0
$rgbCustom = new Rgb(100, 150, 200);
$this->assertEquals(new Cmyk(50, 25, 0, 22), $rgbCustom->toCmyk(), 'RGB Custom to CMYK (rounding check)');
}

/**
* Tests RGB to Gray conversion, focusing on:
* 1. Correct luminance coefficients (0.2126, 0.7152, 0.0722).
* 2. Integer-based calculation to avoid floating-point precision loss.
* 3. Correct application of rounding.
*/
public function testToGray() : void
{
// Black (0, 0, 0) -> Gray(0)
$rgbBlack = new Rgb(0, 0, 0);
$this->assertEquals(new Gray(0), $rgbBlack->toGray(), 'RGB Black to Gray');

// White (255, 255, 255) -> Gray(100)
$rgbWhite = new Rgb(255, 255, 255);
$this->assertEquals(new Gray(100), $rgbWhite->toGray(), 'RGB White to Gray');

// Pure Red (255, 0, 0)
// round((255 * 2126) / 25500) = round(21.26) = 21
$rgbRed = new Rgb(255, 0, 0);
$this->assertEquals(new Gray(21), $rgbRed->toGray(), 'RGB Red to Gray');

// Pure Green (0, 255, 0)
// round((255 * 7152) / 25500) = round(71.52) = 72
$rgbGreen = new Rgb(0, 255, 0);
$this->assertEquals(new Gray(72), $rgbGreen->toGray(), 'RGB Green to Gray');

// Complex Color checking rounding: RGB(100, 150, 200)
// round((100*2126 + 150*7152 + 200*722) / 25500) = round(56.07...) = 56
$rgbCustom = new Rgb(100, 150, 200);
$this->assertEquals(new Gray(56), $rgbCustom->toGray(), 'RGB Custom to Gray (rounding check)');
}
}