diff --git a/src/Canvas/Canvas.php b/src/Canvas/Canvas.php new file mode 100644 index 0000000..da94258 --- /dev/null +++ b/src/Canvas/Canvas.php @@ -0,0 +1,85 @@ +width = $width; + $this->height = $height; + } + + + /** + * @return int + */ + public function width(): int + { + return $this->width; + } + + + /** + * @return int + */ + public function height(): int + { + return $this->height; + } + + + /** + * @return Color[][] + */ + public function pixels(): array + { + return $this->pixels; + } + + + /** + * @param int $x + * @param int $y + * @param Color $color + */ + public function addPixel(int $x, int $y, Color $color): void + { + if ($x < 0 || $x >= $this->width) { + throw new OutOfBoundsException('Invalid position for x provided. Expected integer between 0 and ' + . ($this->width - 1) . ', but got ' . $x); + } elseif ($y < 0 || $y >= $this->height) { + throw new OutOfBoundsException('Invalid position for y provided. Expected integer between 0 and ' + . ($this->height - 1) . ', but got ' . $y); + } + + $this->pixels[$x][$y] = $color; + } +} \ No newline at end of file diff --git a/src/Canvas/CanvasPpmFileWriter.php b/src/Canvas/CanvasPpmFileWriter.php new file mode 100644 index 0000000..063214e --- /dev/null +++ b/src/Canvas/CanvasPpmFileWriter.php @@ -0,0 +1,99 @@ +filesystem = $filesystem; + } + + + /** + * @param Canvas $canvas + * @param string $filename + */ + public function createFile(Canvas $canvas, string $filename): void + { + $content = $this->getFileHeader($canvas); + $content .= $this->getImageContent($canvas); + + $this->filesystem->put($filename, $content); + } + + + /** + * @param Canvas $canvas + * + * @return string + */ + private function getFileHeader(Canvas $canvas): string + { + return 'P3' . PHP_EOL . $canvas->width() . ' ' . $canvas->height() . PHP_EOL . '255' . PHP_EOL; + } + + + /** + * @param Canvas $canvas + * + * @return string + */ + private function getImageContent(Canvas $canvas): string + { + $content = ''; + $pixels = $canvas->pixels(); + + for ($y = 0; $y < $canvas->height(); $y++) { + $rowContent = ''; + for ($x = 0; $x < $canvas->width(); $x++) { + foreach (['red', 'green', 'blue'] as $color) { + $colorHex = '0'; + if (isset($pixels[$x][$y]) && $color === 'red') { + $colorHex = $pixels[$x][$y]->redAsHex(); + } elseif (isset($pixels[$x][$y]) && $color === 'green') { + $colorHex = $pixels[$x][$y]->greenAsHex(); + } elseif (isset($pixels[$x][$y]) && $color === 'blue') { + $colorHex = $pixels[$x][$y]->blueAsHex(); + } + + if (strlen($rowContent . $colorHex) > self::MAX_CHARS_PER_LINE) { + $content .= trim($rowContent) . PHP_EOL; + $rowContent = ''; + } + $rowContent .= $colorHex . ' '; + } + } + $content .= trim($rowContent) . PHP_EOL; + } + + return $content; + } +} \ No newline at end of file diff --git a/tests/Canvas/CanvasPpmFileWriterTest.php b/tests/Canvas/CanvasPpmFileWriterTest.php new file mode 100644 index 0000000..1b58831 --- /dev/null +++ b/tests/Canvas/CanvasPpmFileWriterTest.php @@ -0,0 +1,122 @@ +filesystem = $this->createMock(FilesystemInterface::class); + + $this->fileWriter = new CanvasPpmFileWriter($this->filesystem); + } + + + public function testWritesPPMHeader(): void + { + $expectedFilename = 'my_canvas.ppm'; + $expectedContentLines = [ + 0 => 'P3', + 1 => '5 3', + 2 => '255', + ]; + + $this->filesystem->expects($this->once()) + ->method('put') + ->with($expectedFilename, $this->checkFileContentCallback($expectedContentLines)); + + $canvas = new Canvas(5, 3); + $this->fileWriter->createFile($canvas, $expectedFilename); + } + + + public function testWritesPixels(): void + { + $expectedFilename = 'my_canvas.ppm'; + $expectedContentLines = [ + 3 => '255 0 0 0 0 0 0 0 0 0 0 0 0 0 0', + 4 => '0 0 0 0 0 0 0 128 0 0 0 0 0 0 0', + 5 => '0 0 0 0 0 0 0 0 0 0 0 0 0 0 255', + ]; + + $this->filesystem->expects($this->once()) + ->method('put') + ->with($expectedFilename, $this->checkFileContentCallback($expectedContentLines)); + + $canvas = new Canvas(5, 3); + $canvas->addPixel(0, 0, new Color(1.5, 0.0, 0.0)); + $canvas->addPixel(2, 1, new Color(0.0, 0.5, 0.0)); + $canvas->addPixel(4, 2, new Color(-0.5, 0.0, 1.0)); + + $this->fileWriter->createFile($canvas, $expectedFilename); + } + + + public function testSplitsLongLinesOfPixels(): void + { + $expectedFilename = 'my_canvas.ppm'; + $expectedContentLines = [ + 3 => '255 204 153 255 204 153 255 204 153 255 204 153 255 204 153 255 204', + 4 => '153 255 204 153 255 204 153 255 204 153 255 204 153', + 5 => '255 204 153 255 204 153 255 204 153 255 204 153 255 204 153 255 204', + 6 => '153 255 204 153 255 204 153 255 204 153 255 204 153', + ]; + + $this->filesystem->expects($this->once()) + ->method('put') + ->with($expectedFilename, $this->checkFileContentCallback($expectedContentLines)); + + $canvas = new Canvas(10, 2); + for ($i = 0; $i < 10; $i++) { + $canvas->addPixel($i, 0, new Color(1, 0.8, 0.6)); + $canvas->addPixel($i, 1, new Color(1, 0.8, 0.6)); + } + + $this->fileWriter->createFile($canvas, $expectedFilename); + } + + + /** + * Returns a callback to check the content of the PPM file + * + * @param array $expectedContentLines + * + * @return callable + */ + private function checkFileContentCallback(array $expectedContentLines) + { + return $this->callback(function ( + string $content + ) use ($expectedContentLines): bool { + $actualContentLines = explode(PHP_EOL, $content); + foreach ($expectedContentLines as $line => $expectedContent) { + if ($expectedContent !== $actualContentLines[$line]) { + return false; + } + } + + return true; + }); + } +} diff --git a/tests/Canvas/CanvasTest.php b/tests/Canvas/CanvasTest.php new file mode 100644 index 0000000..e3beb94 --- /dev/null +++ b/tests/Canvas/CanvasTest.php @@ -0,0 +1,58 @@ +assertSame($width, $canvas->width()); + $this->assertSame($height, $canvas->height()); + } + + + public function testProvidesItsPixels(): void + { + $canvas = new Canvas(2, 2); + $canvas->addPixel(0, 0, new Color(1.0, 0.0, 0.0)); + $canvas->addPixel(0, 1, new Color(0.0, 1.0, 0.0)); + $canvas->addPixel(1, 1, new Color(0.0, 0.0, 1.0)); + + $expectedPixels = [ + 0 => [ + 0 => new Color(1.0, 0.0, 0.0), + 1 => new Color(0.0, 1.0, 0.0), + ], + 1 => [ + 1 => new Color(0.0, 0.0, 1.0), + ], + ]; + + $this->assertEquals($expectedPixels, $canvas->pixels()); + } + + + public function testThrowsExceptionIfPixelToAddIsOutOfBounds(): void + { + $this->expectException(OutOfBoundsException::class); + + $canvas = new Canvas(1, 1); + $canvas->addPixel(1, 1, new Color(1.0, 0.0, 0.0)); + } +}