Repository: Intervention/image Branch: develop Commit: c073d1c93a11 Files: 551 Total size: 933.6 KB Directory structure: gitextract_zeq9zksh/ ├── .editorconfig ├── .gitattributes ├── .github/ │ ├── FUNDING.yml │ ├── ISSUE_TEMPLATE/ │ │ ├── bug_report.md │ │ └── feature_request.md │ └── workflows/ │ └── run-tests.yml ├── .gitignore ├── .well-known/ │ └── funding-manifest-urls ├── CONTRIBUTING.md ├── Dockerfile ├── LICENSE ├── composer.json ├── docker-compose.yml ├── phpcs.xml.dist ├── phpstan.dist.neon ├── phpunit.dist.xml ├── readme.md ├── src/ │ ├── Analyzers/ │ │ ├── ColorspaceAnalyzer.php │ │ ├── HeightAnalyzer.php │ │ ├── PixelColorAnalyzer.php │ │ ├── PixelColorsAnalyzer.php │ │ ├── ProfileAnalyzer.php │ │ ├── ResolutionAnalyzer.php │ │ └── WidthAnalyzer.php │ ├── Collection.php │ ├── Colors/ │ │ ├── AbstractColor.php │ │ ├── AbstractColorChannel.php │ │ ├── Cmyk/ │ │ │ ├── Channels/ │ │ │ │ ├── Cyan.php │ │ │ │ ├── Key.php │ │ │ │ ├── Magenta.php │ │ │ │ └── Yellow.php │ │ │ ├── Color.php │ │ │ ├── Colorspace.php │ │ │ └── Decoders/ │ │ │ └── StringColorDecoder.php │ │ ├── Hsl/ │ │ │ ├── Channels/ │ │ │ │ ├── Hue.php │ │ │ │ ├── Luminance.php │ │ │ │ └── Saturation.php │ │ │ ├── Color.php │ │ │ ├── Colorspace.php │ │ │ └── Decoders/ │ │ │ └── StringColorDecoder.php │ │ ├── Hsv/ │ │ │ ├── Channels/ │ │ │ │ ├── Hue.php │ │ │ │ ├── Saturation.php │ │ │ │ └── Value.php │ │ │ ├── Color.php │ │ │ ├── Colorspace.php │ │ │ └── Decoders/ │ │ │ └── StringColorDecoder.php │ │ ├── Profile.php │ │ └── Rgb/ │ │ ├── Channels/ │ │ │ ├── Alpha.php │ │ │ ├── Blue.php │ │ │ ├── Green.php │ │ │ └── Red.php │ │ ├── Color.php │ │ ├── Colorspace.php │ │ └── Decoders/ │ │ ├── HexColorDecoder.php │ │ ├── HtmlColornameDecoder.php │ │ ├── StringColorDecoder.php │ │ └── TransparentColorDecoder.php │ ├── Config.php │ ├── Decoders/ │ │ ├── Base64ImageDecoder.php │ │ ├── BinaryImageDecoder.php │ │ ├── ColorObjectDecoder.php │ │ ├── DataUriImageDecoder.php │ │ ├── EncodedImageObjectDecoder.php │ │ ├── FilePathImageDecoder.php │ │ ├── FilePointerImageDecoder.php │ │ ├── ImageObjectDecoder.php │ │ ├── NativeObjectDecoder.php │ │ └── SplFileInfoImageDecoder.php │ ├── Drivers/ │ │ ├── AbstractDecoder.php │ │ ├── AbstractDriver.php │ │ ├── AbstractEncoder.php │ │ ├── AbstractFontProcessor.php │ │ ├── AbstractFrame.php │ │ ├── Gd/ │ │ │ ├── Analyzers/ │ │ │ │ ├── ColorspaceAnalyzer.php │ │ │ │ ├── HeightAnalyzer.php │ │ │ │ ├── PixelColorAnalyzer.php │ │ │ │ ├── PixelColorsAnalyzer.php │ │ │ │ ├── ResolutionAnalyzer.php │ │ │ │ └── WidthAnalyzer.php │ │ │ ├── Cloner.php │ │ │ ├── ColorProcessor.php │ │ │ ├── Core.php │ │ │ ├── Decoders/ │ │ │ │ ├── AbstractDecoder.php │ │ │ │ ├── Base64ImageDecoder.php │ │ │ │ ├── BinaryImageDecoder.php │ │ │ │ ├── DataUriImageDecoder.php │ │ │ │ ├── EncodedImageObjectDecoder.php │ │ │ │ ├── FilePathImageDecoder.php │ │ │ │ ├── FilePointerImageDecoder.php │ │ │ │ ├── NativeObjectDecoder.php │ │ │ │ └── SplFileInfoImageDecoder.php │ │ │ ├── Driver.php │ │ │ ├── Encoders/ │ │ │ │ ├── AvifEncoder.php │ │ │ │ ├── BmpEncoder.php │ │ │ │ ├── GifEncoder.php │ │ │ │ ├── JpegEncoder.php │ │ │ │ ├── PngEncoder.php │ │ │ │ └── WebpEncoder.php │ │ │ ├── FontProcessor.php │ │ │ ├── Frame.php │ │ │ └── Modifiers/ │ │ │ ├── AlignRotationModifier.php │ │ │ ├── BlendTransparencyModifier.php │ │ │ ├── BlurModifier.php │ │ │ ├── BrightnessModifier.php │ │ │ ├── ColorizeModifier.php │ │ │ ├── ColorspaceModifier.php │ │ │ ├── ContainModifier.php │ │ │ ├── ContrastModifier.php │ │ │ ├── CoverDownModifier.php │ │ │ ├── CoverModifier.php │ │ │ ├── CropModifier.php │ │ │ ├── DrawBezierModifier.php │ │ │ ├── DrawEllipseModifier.php │ │ │ ├── DrawLineModifier.php │ │ │ ├── DrawPixelModifier.php │ │ │ ├── DrawPolygonModifier.php │ │ │ ├── DrawRectangleModifier.php │ │ │ ├── FillModifier.php │ │ │ ├── FlipModifier.php │ │ │ ├── FlopModifier.php │ │ │ ├── GammaModifier.php │ │ │ ├── GreyscaleModifier.php │ │ │ ├── InvertModifier.php │ │ │ ├── PadModifier.php │ │ │ ├── PixelateModifier.php │ │ │ ├── PlaceModifier.php │ │ │ ├── ProfileModifier.php │ │ │ ├── ProfileRemovalModifier.php │ │ │ ├── QuantizeColorsModifier.php │ │ │ ├── RemoveAnimationModifier.php │ │ │ ├── ResizeCanvasModifier.php │ │ │ ├── ResizeCanvasRelativeModifier.php │ │ │ ├── ResizeDownModifier.php │ │ │ ├── ResizeModifier.php │ │ │ ├── ResolutionModifier.php │ │ │ ├── RotateModifier.php │ │ │ ├── ScaleDownModifier.php │ │ │ ├── ScaleModifier.php │ │ │ ├── SharpenModifier.php │ │ │ ├── SliceAnimationModifier.php │ │ │ ├── TextModifier.php │ │ │ └── TrimModifier.php │ │ ├── Imagick/ │ │ │ ├── Analyzers/ │ │ │ │ ├── ColorspaceAnalyzer.php │ │ │ │ ├── HeightAnalyzer.php │ │ │ │ ├── PixelColorAnalyzer.php │ │ │ │ ├── PixelColorsAnalyzer.php │ │ │ │ ├── ProfileAnalyzer.php │ │ │ │ ├── ResolutionAnalyzer.php │ │ │ │ └── WidthAnalyzer.php │ │ │ ├── ColorProcessor.php │ │ │ ├── Core.php │ │ │ ├── Decoders/ │ │ │ │ ├── Base64ImageDecoder.php │ │ │ │ ├── BinaryImageDecoder.php │ │ │ │ ├── DataUriImageDecoder.php │ │ │ │ ├── EncodedImageObjectDecoder.php │ │ │ │ ├── FilePathImageDecoder.php │ │ │ │ ├── FilePointerImageDecoder.php │ │ │ │ ├── NativeObjectDecoder.php │ │ │ │ └── SplFileInfoImageDecoder.php │ │ │ ├── Driver.php │ │ │ ├── Encoders/ │ │ │ │ ├── AvifEncoder.php │ │ │ │ ├── BmpEncoder.php │ │ │ │ ├── GifEncoder.php │ │ │ │ ├── HeicEncoder.php │ │ │ │ ├── Jpeg2000Encoder.php │ │ │ │ ├── JpegEncoder.php │ │ │ │ ├── PngEncoder.php │ │ │ │ ├── TiffEncoder.php │ │ │ │ └── WebpEncoder.php │ │ │ ├── FontProcessor.php │ │ │ ├── Frame.php │ │ │ └── Modifiers/ │ │ │ ├── AlignRotationModifier.php │ │ │ ├── BlendTransparencyModifier.php │ │ │ ├── BlurModifier.php │ │ │ ├── BrightnessModifier.php │ │ │ ├── ColorizeModifier.php │ │ │ ├── ColorspaceModifier.php │ │ │ ├── ContainModifier.php │ │ │ ├── ContrastModifier.php │ │ │ ├── CoverDownModifier.php │ │ │ ├── CoverModifier.php │ │ │ ├── CropModifier.php │ │ │ ├── DrawBezierModifier.php │ │ │ ├── DrawEllipseModifier.php │ │ │ ├── DrawLineModifier.php │ │ │ ├── DrawPixelModifier.php │ │ │ ├── DrawPolygonModifier.php │ │ │ ├── DrawRectangleModifier.php │ │ │ ├── FillModifier.php │ │ │ ├── FlipModifier.php │ │ │ ├── FlopModifier.php │ │ │ ├── GammaModifier.php │ │ │ ├── GreyscaleModifier.php │ │ │ ├── InvertModifier.php │ │ │ ├── PadModifier.php │ │ │ ├── PixelateModifier.php │ │ │ ├── PlaceModifier.php │ │ │ ├── ProfileModifier.php │ │ │ ├── ProfileRemovalModifier.php │ │ │ ├── QuantizeColorsModifier.php │ │ │ ├── RemoveAnimationModifier.php │ │ │ ├── ResizeCanvasModifier.php │ │ │ ├── ResizeCanvasRelativeModifier.php │ │ │ ├── ResizeDownModifier.php │ │ │ ├── ResizeModifier.php │ │ │ ├── ResolutionModifier.php │ │ │ ├── RotateModifier.php │ │ │ ├── ScaleDownModifier.php │ │ │ ├── ScaleModifier.php │ │ │ ├── SharpenModifier.php │ │ │ ├── SliceAnimationModifier.php │ │ │ ├── StripMetaModifier.php │ │ │ ├── TextModifier.php │ │ │ └── TrimModifier.php │ │ ├── Specializable.php │ │ ├── SpecializableAnalyzer.php │ │ ├── SpecializableDecoder.php │ │ ├── SpecializableEncoder.php │ │ └── SpecializableModifier.php │ ├── EncodedImage.php │ ├── Encoders/ │ │ ├── AutoEncoder.php │ │ ├── AvifEncoder.php │ │ ├── BmpEncoder.php │ │ ├── FileExtensionEncoder.php │ │ ├── FilePathEncoder.php │ │ ├── GifEncoder.php │ │ ├── HeicEncoder.php │ │ ├── Jpeg2000Encoder.php │ │ ├── JpegEncoder.php │ │ ├── MediaTypeEncoder.php │ │ ├── PngEncoder.php │ │ ├── TiffEncoder.php │ │ └── WebpEncoder.php │ ├── Exceptions/ │ │ ├── AnimationException.php │ │ ├── ColorException.php │ │ ├── DecoderException.php │ │ ├── DriverException.php │ │ ├── EncoderException.php │ │ ├── FontException.php │ │ ├── GeometryException.php │ │ ├── InputException.php │ │ ├── NotSupportedException.php │ │ ├── NotWritableException.php │ │ └── RuntimeException.php │ ├── File.php │ ├── FileExtension.php │ ├── Format.php │ ├── Geometry/ │ │ ├── Bezier.php │ │ ├── Circle.php │ │ ├── Ellipse.php │ │ ├── Factories/ │ │ │ ├── BezierFactory.php │ │ │ ├── CircleFactory.php │ │ │ ├── Drawable.php │ │ │ ├── EllipseFactory.php │ │ │ ├── LineFactory.php │ │ │ ├── PolygonFactory.php │ │ │ └── RectangleFactory.php │ │ ├── Line.php │ │ ├── Pixel.php │ │ ├── Point.php │ │ ├── Polygon.php │ │ ├── Rectangle.php │ │ ├── Tools/ │ │ │ └── RectangleResizer.php │ │ └── Traits/ │ │ ├── HasBackgroundColor.php │ │ └── HasBorder.php │ ├── Image.php │ ├── ImageManager.php │ ├── InputHandler.php │ ├── Interfaces/ │ │ ├── AnalyzerInterface.php │ │ ├── CollectionInterface.php │ │ ├── ColorChannelInterface.php │ │ ├── ColorInterface.php │ │ ├── ColorProcessorInterface.php │ │ ├── ColorspaceInterface.php │ │ ├── CoreInterface.php │ │ ├── DecoderInterface.php │ │ ├── DrawableFactoryInterface.php │ │ ├── DrawableInterface.php │ │ ├── DriverInterface.php │ │ ├── EncodedImageInterface.php │ │ ├── EncoderInterface.php │ │ ├── FileInterface.php │ │ ├── FontInterface.php │ │ ├── FontProcessorInterface.php │ │ ├── FrameInterface.php │ │ ├── ImageInterface.php │ │ ├── ImageManagerInterface.php │ │ ├── InputHandlerInterface.php │ │ ├── ModifierInterface.php │ │ ├── PointInterface.php │ │ ├── ProfileInterface.php │ │ ├── ResolutionInterface.php │ │ ├── SizeInterface.php │ │ ├── SpecializableInterface.php │ │ └── SpecializedInterface.php │ ├── MediaType.php │ ├── ModifierStack.php │ ├── Modifiers/ │ │ ├── AbstractDrawModifier.php │ │ ├── AlignRotationModifier.php │ │ ├── BlendTransparencyModifier.php │ │ ├── BlurModifier.php │ │ ├── BrightnessModifier.php │ │ ├── ColorizeModifier.php │ │ ├── ColorspaceModifier.php │ │ ├── ContainModifier.php │ │ ├── ContrastModifier.php │ │ ├── CoverDownModifier.php │ │ ├── CoverModifier.php │ │ ├── CropModifier.php │ │ ├── DrawBezierModifier.php │ │ ├── DrawEllipseModifier.php │ │ ├── DrawLineModifier.php │ │ ├── DrawPixelModifier.php │ │ ├── DrawPolygonModifier.php │ │ ├── DrawRectangleModifier.php │ │ ├── FillModifier.php │ │ ├── FlipModifier.php │ │ ├── FlopModifier.php │ │ ├── GammaModifier.php │ │ ├── GreyscaleModifier.php │ │ ├── InvertModifier.php │ │ ├── PadModifier.php │ │ ├── PixelateModifier.php │ │ ├── PlaceModifier.php │ │ ├── ProfileModifier.php │ │ ├── ProfileRemovalModifier.php │ │ ├── QuantizeColorsModifier.php │ │ ├── RemoveAnimationModifier.php │ │ ├── ResizeCanvasModifier.php │ │ ├── ResizeCanvasRelativeModifier.php │ │ ├── ResizeDownModifier.php │ │ ├── ResizeModifier.php │ │ ├── ResolutionModifier.php │ │ ├── RotateModifier.php │ │ ├── ScaleDownModifier.php │ │ ├── ScaleModifier.php │ │ ├── SharpenModifier.php │ │ ├── SliceAnimationModifier.php │ │ ├── TextModifier.php │ │ └── TrimModifier.php │ ├── Origin.php │ ├── Resolution.php │ ├── Traits/ │ │ ├── CanBeDriverSpecialized.php │ │ └── CanBuildFilePointer.php │ └── Typography/ │ ├── Font.php │ ├── FontFactory.php │ ├── Line.php │ └── TextBlock.php └── tests/ ├── BaseTestCase.php ├── Feature/ │ ├── Gd/ │ │ └── ConvertPngGif.php │ └── Imagick/ │ ├── ConvertPngGif.php │ └── CropResizePngTest.php ├── GdTestCase.php ├── ImagickTestCase.php ├── Traits/ │ ├── CanDetectProgressiveJpeg.php │ └── CanInspectPngFormat.php └── Unit/ ├── CollectionTest.php ├── Colors/ │ ├── Cmyk/ │ │ ├── ChannelTest.php │ │ ├── ColorTest.php │ │ ├── ColorspaceTest.php │ │ └── Decoders/ │ │ └── StringColorDecoderTest.php │ ├── Hsl/ │ │ ├── ChannelTest.php │ │ ├── Channels/ │ │ │ └── SaturationTest.php │ │ ├── ColorTest.php │ │ ├── ColorspaceTest.php │ │ └── Decoders/ │ │ └── StringColorDecoderTest.php │ ├── Hsv/ │ │ ├── ChannelTest.php │ │ ├── Channels/ │ │ │ ├── SaturationTest.php │ │ │ └── ValueTest.php │ │ ├── ColorTest.php │ │ ├── ColorspaceTest.php │ │ └── Decoders/ │ │ └── StringColorDecoderTest.php │ ├── ProfileTest.php │ └── Rgb/ │ ├── ChannelTest.php │ ├── Channels/ │ │ └── AlphaTest.php │ ├── ColorTest.php │ ├── ColorspaceTest.php │ └── Decoders/ │ ├── HexColorDecoderTest.php │ ├── HtmlColornameDecoderTest.php │ └── StringColorDecoderTest.php ├── ConfigTest.php ├── Drivers/ │ ├── AbstractDecoderTest.php │ ├── AbstractEncoderTest.php │ ├── AbstractFontProcessorTest.php │ ├── Gd/ │ │ ├── Analyzers/ │ │ │ ├── ColorspaceAnalyzerTest.php │ │ │ ├── HeightAnalyzerTest.php │ │ │ ├── PixelColorAnalyzerTest.php │ │ │ ├── PixelColorsAnalyzerTest.php │ │ │ ├── ResolutionAnalyzerTest.php │ │ │ └── WidthAnalyzerTest.php │ │ ├── ClonerTest.php │ │ ├── ColorProcessorTest.php │ │ ├── CoreTest.php │ │ ├── Decoders/ │ │ │ ├── AbstractDecoderTest.php │ │ │ ├── Base64ImageDecoderTest.php │ │ │ ├── BinaryImageDecoderTest.php │ │ │ ├── DataUriImageDecoderTest.php │ │ │ ├── EncodedImageObjectDecoderTest.php │ │ │ ├── FilePathImageDecoderTest.php │ │ │ ├── FilePointerImageDecoderTest.php │ │ │ ├── ImageObjectDecoderTest.php │ │ │ ├── NativeObjectDecoderTest.php │ │ │ └── SplFileInfoImageDecoderTest.php │ │ ├── DriverTest.php │ │ ├── Encoders/ │ │ │ ├── AvifEncoderTest.php │ │ │ ├── BmpEncoderTest.php │ │ │ ├── GifEncoderTest.php │ │ │ ├── JpegEncoderTest.php │ │ │ ├── PngEncoderTest.php │ │ │ └── WebpEncoderTest.php │ │ ├── FontProcessorTest.php │ │ ├── FrameTest.php │ │ ├── ImageTest.php │ │ └── Modifiers/ │ │ ├── BlurModifierTest.php │ │ ├── BrightnessModifierTest.php │ │ ├── ColorizeModifierTest.php │ │ ├── ContainModifierTest.php │ │ ├── ContrastModifierTest.php │ │ ├── CoverDownModifierTest.php │ │ ├── CoverModifierTest.php │ │ ├── CropModifierTest.php │ │ ├── DrawBezierModifierTest.php │ │ ├── DrawEllipseModifierTest.php │ │ ├── DrawLineModifierTest.php │ │ ├── DrawPixelModifierTest.php │ │ ├── DrawPolygonModifierTest.php │ │ ├── DrawRectangleModifierTest.php │ │ ├── FillModifierTest.php │ │ ├── FlipFlopModifierTest.php │ │ ├── GammaModifierTest.php │ │ ├── GreyscaleModifierTest.php │ │ ├── InvertModifierTest.php │ │ ├── PadModifierTest.php │ │ ├── PixelateModifierTest.php │ │ ├── PlaceModifierTest.php │ │ ├── QuantizeColorsModifierTest.php │ │ ├── RemoveAnimationModifierTest.php │ │ ├── ResizeCanvasModifierTest.php │ │ ├── ResizeCanvasRelativeModifierTest.php │ │ ├── ResizeModifierTest.php │ │ ├── ResolutionModifierTest.php │ │ ├── RotateModifierTest.php │ │ ├── SharpenModifierTest.php │ │ ├── TextModifierTest.php │ │ └── TrimModifierTest.php │ ├── Imagick/ │ │ ├── Analyzers/ │ │ │ ├── ColorspaceAnalyzerTest.php │ │ │ ├── HeightAnalyzerTest.php │ │ │ ├── PixelColorAnalyzerTest.php │ │ │ ├── PixelColorsAnalyzerTest.php │ │ │ ├── ProfileAnalyzerTest.php │ │ │ ├── ResolutionAnalyzerTest.php │ │ │ └── WidthAnalyzerTest.php │ │ ├── ColorProcessorTest.php │ │ ├── CoreTest.php │ │ ├── Decoders/ │ │ │ ├── Base64ImageDecoderTest.php │ │ │ ├── BinaryImageDecoderTest.php │ │ │ ├── DataUriImageDecoderTest.php │ │ │ ├── EncodedImageObjectDecoderTest.php │ │ │ ├── FilePathImageDecoderTest.php │ │ │ ├── FilePointerImageDecoderTest.php │ │ │ ├── ImageObjectDecoderTest.php │ │ │ ├── NativeObjectDecoderTest.php │ │ │ └── SplFileInfoImageDecoderTest.php │ │ ├── DriverTest.php │ │ ├── Encoders/ │ │ │ ├── AvifEncoderTest.php │ │ │ ├── BmpEncoderTest.php │ │ │ ├── GifEncoderTest.php │ │ │ ├── HeicEncoderTest.php │ │ │ ├── Jpeg2000EncoderTest.php │ │ │ ├── JpegEncoderTest.php │ │ │ ├── PngEncoderTest.php │ │ │ ├── TiffEncoderTest.php │ │ │ └── WebpEncoderTest.php │ │ ├── FontProcessorTest.php │ │ ├── FrameTest.php │ │ ├── ImageTest.php │ │ └── Modifiers/ │ │ ├── BlurModifierTest.php │ │ ├── BrightnessModifierTest.php │ │ ├── ColorizeModifierTest.php │ │ ├── ContainModifierTest.php │ │ ├── ContrastModifierTest.php │ │ ├── CoverDownModifierTest.php │ │ ├── CoverModifierTest.php │ │ ├── CropModifierTest.php │ │ ├── DrawBezierModifierTest.php │ │ ├── DrawEllipseModifierTest.php │ │ ├── DrawLineModifierTest.php │ │ ├── DrawPixelModifierTest.php │ │ ├── DrawPolygonModifierTest.php │ │ ├── DrawRectangleModifierTest.php │ │ ├── FillModifierTest.php │ │ ├── FlipFlopModifierTest.php │ │ ├── GammaModifierTest.php │ │ ├── GreyscaleModifierTest.php │ │ ├── InvertModifierTest.php │ │ ├── PadModifierTest.php │ │ ├── PixelateModifierTest.php │ │ ├── PlaceModifierTest.php │ │ ├── QuantizeColorsModifierTest.php │ │ ├── RemoveAnimationModifierTest.php │ │ ├── ResizeCanvasModifierTest.php │ │ ├── ResizeCanvasRelativeModifierTest.php │ │ ├── ResizeModifierTest.php │ │ ├── ResolutionModifierTest.php │ │ ├── RotateModifierTest.php │ │ ├── SharpenModifierTest.php │ │ ├── StripMetaModifierTest.php │ │ ├── TextModifierTest.php │ │ └── TrimModifierTest.php │ ├── SpecializableAnalyzerTest.php │ ├── SpecializableDecoderTest.php │ └── SpecializableModifierTest.php ├── EncodedImageTest.php ├── Encoders/ │ ├── FileExtensionEncoderTest.php │ └── MediaTypeEncoderTest.php ├── FileExtensionTest.php ├── FileTest.php ├── FormatTest.php ├── Geometry/ │ ├── BezierTest.php │ ├── CircleTest.php │ ├── EllipseTest.php │ ├── Factories/ │ │ ├── BezierFactoryTest.php │ │ ├── CircleFactoryTest.php │ │ ├── DrawableTest.php │ │ ├── EllipseFactoryTest.php │ │ ├── LineFactoryTest.php │ │ ├── PolygonFactoryTest.php │ │ └── RectangleFactoryTest.php │ ├── LineTest.php │ ├── PixelTest.php │ ├── PointTest.php │ ├── PolygonTest.php │ ├── RectangleResizerTest.php │ ├── RectangleTest.php │ └── Traits/ │ ├── HasBackgroundColorTest.php │ └── HasBorderTest.php ├── ImageManagerTestGd.php ├── ImageManagerTestImagick.php ├── InputHandlerTest.php ├── MediaTypeTest.php ├── ModifierStackTest.php ├── Modifiers/ │ ├── ColorspaceModifierTest.php │ ├── PadModifierTest.php │ ├── RemoveAnimationModifierTest.php │ └── TextModifierTest.php ├── OriginTest.php ├── ResolutionTest.php └── Typography/ ├── FontFactoryTest.php ├── FontTest.php ├── LineTest.php └── TextBlockTest.php ================================================ FILE CONTENTS ================================================ ================================================ FILE: .editorconfig ================================================ # editorconfig.org root = true [*.php] indent_size = 4 indent_style = space indent_size = 4 end_of_line = lf charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true ================================================ FILE: .gitattributes ================================================ * text=auto /.editorconfig export-ignore /.github export-ignore /.well-known export-ignore /CONTRIBUTING.md export-ignore /tests export-ignore /.gitattributes export-ignore /.gitignore export-ignore /phpcs.xml.dist export-ignore /phpunit.dist.xml export-ignore /phpstan.dist.neon export-ignore /docker-compose.yml export-ignore /Dockerfile export-ignore ================================================ FILE: .github/FUNDING.yml ================================================ github: [Intervention] ko_fi: interventionphp custom: https://paypal.me/interventionio ================================================ FILE: .github/ISSUE_TEMPLATE/bug_report.md ================================================ --- name: Bug report about: Create a report to help us improve title: '' labels: '' assignees: '' --- ## Describe the bug A clear and concise description of what the bug is. ## Code Example Code example to reproduce the behavior. ## Expected behavior A clear and concise description of what you expected to happen. ## Images If applicable, add problematic images or screenshots to help explain your problem. ## Environment (please complete the following information): - PHP Version: - OS: - Intervention Image Version: - GD, Imagick or libvips: ================================================ FILE: .github/ISSUE_TEMPLATE/feature_request.md ================================================ --- name: Feature request about: Suggest an idea for this project title: '' labels: '' assignees: '' --- ## Describe the feature you'd like A clear and concise description of what you want to happen. ## Is your feature request related to a problem? Please describe. A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] ================================================ FILE: .github/workflows/run-tests.yml ================================================ name: Tests on: [ push, pull_request ] jobs: run: runs-on: ubuntu-22.04 strategy: fail-fast: false matrix: php: [ '8.1', '8.2', '8.3', '8.4', '8.5' ] imagemagick: [ '6.9.13-25', '7.1.1-47' ] name: PHP ${{ matrix.php }} - ImageMagick ${{ matrix.imagemagick }} steps: - name: Checkout project uses: actions/checkout@v4 - name: Setup PHP uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php }} extensions: mbstring, gd coverage: none - name: Prepare environment for Imagemagick run: | sudo apt remove -y imagemagick imagemagick-6-common libmagic-dev sudo apt update --allow-releaseinfo-change sudo apt update sudo apt install -y libjpeg-dev libgif-dev libtiff-dev libpng-dev libwebp-dev libavif-dev libheif-dev libraqm-dev libmagickwand-dev - name: Cache ImageMagick uses: actions/cache@v4 id: cache-imagemagick with: path: /home/runner/im/imagemagick-${{ matrix.imagemagick }} key: ${{ runner.os }}-ImageMagick-${{ matrix.imagemagick }}-${{ hashFiles('**/composer.json') }} restore-keys: ${{ runner.os }}-ImageMagick-${{ matrix.imagemagick }}- - name: Check ImageMagick cache exists uses: andstor/file-existence-action@v3 id: cache-imagemagick-exists with: files: /home/runner/im/imagemagick-${{ matrix.imagemagick }} - name: Install ImageMagick if: ( steps.cache-imagemagick.outputs.cache-hit != 'true' || steps.cache-imagemagick-exists.outputs.files_exists != 'true' ) run: | curl -o /tmp/ImageMagick.tar.xz -sL https://imagemagick.org/archive/releases/ImageMagick-${{ matrix.imagemagick }}.tar.xz ( cd /tmp || exit 1 tar xf ImageMagick.tar.xz cd ImageMagick-${{ matrix.imagemagick }} sudo ./configure --prefix=/home/runner/im/imagemagick-${{ matrix.imagemagick }} sudo make -j$(nproc) sudo make install ) - name: Install Imagick PHP extension run: | git clone --depth=1 --single-branch --branch master https://github.com/Imagick/imagick /tmp/imagick ( cd /tmp/imagick || exit 1 phpize sudo ./configure --with-imagick=/home/runner/im/imagemagick-${{ matrix.imagemagick }} sudo make -j$(nproc) sudo make install ) sudo bash -c 'echo "extension=imagick.so" >> /etc/php/${{ matrix.php }}/cli/php.ini' php --ri imagick; - name: Get composer cache directory id: composer-cache run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT - name: Cache composer dependencies uses: actions/cache@v4 with: path: ${{ steps.composer-cache.outputs.dir }} key: ${{ runner.os }}-php-${{ matrix.php }}-${{ hashFiles('**/composer.json') }} restore-keys: ${{ runner.os }}-php-${{ matrix.php }}- - name: Install dependencies run: composer update --prefer-stable --prefer-dist --no-interaction - name: GD Version run: php -r 'var_dump(gd_info());' - name: Imagick Version run: php -r 'var_dump(Imagick::getVersion());' - name: Supported Imagick Formats run: php -r 'var_dump(Imagick::queryFormats());' - name: Execute tests run: vendor/bin/phpunit --no-coverage - name: Run analyzer run: vendor/bin/phpstan - name: Validate coding standards run: vendor/bin/phpcs ================================================ FILE: .gitignore ================================================ build/ vendor/ /.phpunit.cache composer.lock phpunit.xml phpcs.xml phpstan.neon ================================================ FILE: .well-known/funding-manifest-urls ================================================ https://intervention.io/funding.json https://image.intervention.io/funding.json ================================================ FILE: CONTRIBUTING.md ================================================ # Contributing to Intervention Image Thank you for your interest in contributing to the project. You can read the full contribution guide on https://intervention.io/contributing/. ================================================ FILE: Dockerfile ================================================ FROM php:8.1-cli # install dependencies RUN apt update \ && apt install -y \ libmagickwand-dev \ libwebp-dev \ libpng-dev \ libavif-dev \ git \ zip \ && pecl install imagick \ && pecl install xdebug \ && docker-php-ext-configure gd --with-freetype --with-jpeg --with-webp --with-avif \ && docker-php-ext-enable \ imagick \ xdebug \ && docker-php-ext-install \ gd \ exif \ && apt-get clean # install composer COPY --from=composer /usr/bin/composer /usr/bin/composer # install composer dependencies COPY composer.json composer.lock ./ RUN composer install ================================================ FILE: LICENSE ================================================ The MIT License (MIT) Copyright (c) 2013-present Oliver Vogel Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: composer.json ================================================ { "name": "intervention/image", "description": "PHP Image Processing", "homepage": "https://image.intervention.io", "keywords": [ "image", "gd", "imagick", "watermark", "thumbnail", "resize" ], "license": "MIT", "authors": [ { "name": "Oliver Vogel", "email": "oliver@intervention.io", "homepage": "https://intervention.io" } ], "require": { "php": "^8.1", "ext-mbstring": "*", "intervention/gif": "^4.2" }, "require-dev": { "phpunit/phpunit": "^10.0 || ^11.0 || ^12.0", "mockery/mockery": "^1.6", "phpstan/phpstan": "^2.1", "squizlabs/php_codesniffer": "^4", "slevomat/coding-standard": "~8.0" }, "suggest": { "ext-exif": "Recommended to be able to read EXIF data properly." }, "autoload": { "psr-4": { "Intervention\\Image\\": "src" } }, "autoload-dev": { "psr-4": { "Intervention\\Image\\Tests\\": "tests" } }, "config": { "allow-plugins": { "dealerdirect/phpcodesniffer-composer-installer": true } } } ================================================ FILE: docker-compose.yml ================================================ services: tests: build: ./ working_dir: /project entrypoint: ["./vendor/bin/phpunit"] volumes: - ./:/project coverage: build: ./ working_dir: /project entrypoint: ["./vendor/bin/phpunit", "--coverage-text"] volumes: - ./:/project environment: - XDEBUG_MODE=coverage analysis: build: ./ working_dir: /project entrypoint: ["./vendor/bin/phpstan", "analyze", "--memory-limit=512M"] volumes: - ./:/project standards: build: ./ working_dir: /project entrypoint: ["./vendor/bin/phpcs"] volumes: - ./:/project ================================================ FILE: phpcs.xml.dist ================================================ src/ tests/ ================================================ FILE: phpstan.dist.neon ================================================ parameters: level: 6 paths: - src reportUnmatchedIgnoredErrors: false ignoreErrors: - identifier: unset.possiblyHookedProperty exceptions: check: missingCheckedExceptionInThrows: true uncheckedExceptionClasses: - ImagickException - ImagickDrawException - ImagickPixelException - Error ================================================ FILE: phpunit.dist.xml ================================================ ./tests/Unit ./tests/Feature src ================================================ FILE: readme.md ================================================ # Intervention Image ## PHP Image Processing [![Latest Version](https://img.shields.io/packagist/v/intervention/image.svg)](https://packagist.org/packages/intervention/image) [![Build Status](https://github.com/Intervention/image/actions/workflows/run-tests.yml/badge.svg)](https://github.com/Intervention/image/actions) [![Monthly Downloads](https://img.shields.io/packagist/dm/intervention/image.svg)](https://packagist.org/packages/intervention/image/stats) [![Support me on Ko-fi](https://raw.githubusercontent.com/Intervention/image/develop/.github/images/support.svg)](https://ko-fi.com/interventionphp) Intervention Image is a **PHP image processing library** that provides a simple and expressive way to create, edit, and compose images. It comes with a universal interface for the two most popular PHP image manipulation extensions. You can choose between the GD library or Imagick as the base layer for all operations. - Simple interface for common image editing tasks - Interchangeable driver architecture - Support for animated images - Framework-agnostic - PSR-12 compliant ## Installation You can easily install this library using [Composer](https://getcomposer.org). Simply request the package with the following command: ```bash composer require intervention/image ``` ## Getting Started Learn the [basics](https://image.intervention.io/v3/basics/instantiation/) on how to use Intervention Image and more with the [official documentation](https://image.intervention.io/v3/). ## Code Examples ```php use Intervention\Image\ImageManager; // create image manager with desired driver $manager = new ImageManager( new Intervention\Image\Drivers\Gd\Driver() ); // open an image file $image = $manager->read('images/example.gif'); // resize image instance $image->resize(height: 300); // insert a watermark $image->place('images/watermark.png'); // encode edited image $encoded = $image->toJpg(); // save encoded image $encoded->save('images/example.jpg'); ``` ## Requirements Before you begin with the installation make sure that your server environment supports the following requirements. - PHP >= 8.1 - Mbstring PHP Extension - Image Processing PHP Extension ## Supported Image Libraries Depending on your environment Intervention Image lets you choose between different image processing extensions. - GD Library - Imagick PHP extension - [libvips](https://github.com/Intervention/image-driver-vips) ## Security If you discover any security related issues, please email oliver@intervention.io directly. ## Authors This library is developed and maintained by [Oliver Vogel](https://intervention.io) Thanks to the community of [contributors](https://github.com/Intervention/image/graphs/contributors) who have helped to improve this project. ## License Intervention Image is licensed under the [MIT License](LICENSE). ================================================ FILE: src/Analyzers/ColorspaceAnalyzer.php ================================================ */ class Collection implements CollectionInterface, IteratorAggregate, Countable { /** * Create new collection object * * @param array $items * @return void */ public function __construct(protected array $items = []) { // } /** * Static constructor * * @param array $items * @return self */ public static function create(array $items = []): self { return new self($items); } /** * {@inheritdoc} * * @see CollectionInterface::has() */ public function has(int|string $key): bool { return array_key_exists($key, $this->items); } /** * Returns Iterator * * @return Traversable */ public function getIterator(): Traversable { return new ArrayIterator($this->items); } /** * {@inheritdoc} * * @see CollectionInterface::toArray() */ public function toArray(): array { return $this->items; } /** * Count items in collection */ public function count(): int { return count($this->items); } /** * Append new item to collection * * @return CollectionInterface */ public function push(mixed $item): CollectionInterface { $this->items[] = $item; return $this; } /** * Return first item in collection */ public function first(): mixed { if ($item = reset($this->items)) { return $item; } return null; } /** * Returns last item in collection */ public function last(): mixed { if ($item = end($this->items)) { return $item; } return null; } /** * Return item at given position starting at 0 */ public function getAtPosition(int $key = 0, mixed $default = null): mixed { if ($this->count() == 0) { return $default; } $positions = array_values($this->items); if (!array_key_exists($key, $positions)) { return $default; } return $positions[$key]; } /** * {@inheritdoc} * * @see CollectionInterface::get() */ public function get(int|string $query, mixed $default = null): mixed { if ($this->count() == 0) { return $default; } if (is_int($query) && array_key_exists($query, $this->items)) { return $this->items[$query]; } if (is_string($query) && !str_contains($query, '.')) { return array_key_exists($query, $this->items) ? $this->items[$query] : $default; } $query = explode('.', (string) $query); $result = $default; $items = $this->items; foreach ($query as $key) { if (!is_array($items) || !array_key_exists($key, $items)) { $result = $default; break; } $result = $items[$key]; $items = $result; } return $result; } /** * Map each item of collection by given callback */ public function map(callable $callback): self { return new self( array_map( fn(mixed $item) => $callback($item), $this->items, ) ); } /** * Run callback on each item of the collection an remove it if it does not return true */ public function filter(callable $callback): self { return new self( array_filter( $this->items, fn(mixed $item) => $callback($item), ) ); } /** * {@inheritdoc} * * @see CollectionInterface::empty() */ public function empty(): CollectionInterface { $this->items = []; return $this; } /** * {@inheritdoc} * * @see CollectionInterface::slice() */ public function slice(int $offset, ?int $length = null): CollectionInterface { $this->items = array_slice($this->items, $offset, $length); return $this; } } ================================================ FILE: src/Colors/AbstractColor.php ================================================ */ protected array $channels; /** * {@inheritdoc} * * @see ColorInterface::channels() */ public function channels(): array { return $this->channels; } /** * {@inheritdoc} * * @see ColorInterface::channel() */ public function channel(string $classname): ColorChannelInterface { $channels = array_filter( $this->channels(), fn(ColorChannelInterface $channel): bool => $channel::class === $classname, ); if (count($channels) == 0) { throw new ColorException('Color channel ' . $classname . ' could not be found.'); } return reset($channels); } /** * {@inheritdoc} * * @see ColorInterface::normalize() */ public function normalize(): array { return array_map( fn(ColorChannelInterface $channel): float => $channel->normalize(), $this->channels(), ); } /** * {@inheritdoc} * * @see ColorInterface::toArray() */ public function toArray(): array { return array_map( fn(ColorChannelInterface $channel): int => $channel->value(), $this->channels() ); } /** * {@inheritdoc} * * @see ColorInterface::convertTo() */ public function convertTo(string|ColorspaceInterface $colorspace): ColorInterface { $colorspace = match (true) { is_object($colorspace) => $colorspace, default => new $colorspace(), }; return $colorspace->importColor($this); } /** * Show debug info for the current color * * @return array */ public function __debugInfo(): array { return array_reduce($this->channels(), function (array $result, ColorChannelInterface $item) { $key = strtolower((new ReflectionClass($item))->getShortName()); $result[$key] = $item->value(); return $result; }, []); } /** * {@inheritdoc} * * @see ColorInterface::__toString() */ public function __toString(): string { return $this->toString(); } } ================================================ FILE: src/Colors/AbstractColorChannel.php ================================================ value = $this->validate( match (true) { is_null($value) && is_numeric($normalized) => intval(round($normalized * $this->max())), is_numeric($value) && is_null($normalized) => $value, default => throw new ColorException('Color channels must either have a value or a normalized value') } ); } /** * Alias of value() */ public function toInt(): int { return $this->value; } /** * {@inheritdoc} * * @see ColorChannelInterface::value() */ public function value(): int { return $this->value; } /** * {@inheritdoc} * * @see ColorChannelInterface::normalize() */ public function normalize(int $precision = 32): float { return round(($this->value() - $this->min()) / ($this->max() - $this->min()), $precision); } /** * {@inheritdoc} * * @see ColorChannelInterface::validate() */ public function validate(mixed $value): mixed { if ($value < $this->min() || $value > $this->max()) { throw new ColorException('Color channel value must be in range ' . $this->min() . ' to ' . $this->max()); } return $value; } /** * {@inheritdoc} * * @see ColorChannelInterface::toString() */ public function toString(): string { return (string) $this->value(); } /** * {@inheritdoc} * * @see ColorChannelInterface::__toString() */ public function __toString(): string { return $this->toString(); } } ================================================ FILE: src/Colors/Cmyk/Channels/Cyan.php ================================================ channels = [ new Cyan($c), new Magenta($m), new Yellow($y), new Key($k), ]; } /** * {@inheritdoc} * * @see ColorInterface::create() */ public static function create(mixed $input): ColorInterface { return InputHandler::withDecoders([ Decoders\StringColorDecoder::class, ])->handle($input); } /** * {@inheritdoc} * * @see ColorInterface::colorspace() */ public function colorspace(): ColorspaceInterface { return new Colorspace(); } /** * {@inheritdoc} * * @see ColorInterface::toHex() */ public function toHex(string $prefix = ''): string { return $this->convertTo(RgbColorspace::class)->toHex($prefix); } /** * Return the CMYK cyan channel */ public function cyan(): ColorChannelInterface { /** @throws void */ return $this->channel(Cyan::class); } /** * Return the CMYK magenta channel */ public function magenta(): ColorChannelInterface { /** @throws void */ return $this->channel(Magenta::class); } /** * Return the CMYK yellow channel */ public function yellow(): ColorChannelInterface { /** @throws void */ return $this->channel(Yellow::class); } /** * Return the CMYK key channel */ public function key(): ColorChannelInterface { /** @throws void */ return $this->channel(Key::class); } /** * {@inheritdoc} * * @see ColorInterface::toString() */ public function toString(): string { return sprintf( 'cmyk(%d%%, %d%%, %d%%, %d%%)', $this->cyan()->value(), $this->magenta()->value(), $this->yellow()->value(), $this->key()->value() ); } /** * {@inheritdoc} * * @see ColorInterface::isGreyscale() */ public function isGreyscale(): bool { return 0 === array_sum([ $this->cyan()->value(), $this->magenta()->value(), $this->yellow()->value(), ]); } /** * {@inheritdoc} * * @see ColorInterface::isTransparent() */ public function isTransparent(): bool { return false; } /** * {@inheritdoc} * * @see ColorInterface::isClear() */ public function isClear(): bool { return false; } } ================================================ FILE: src/Colors/Cmyk/Colorspace.php ================================================ */ public static array $channels = [ Channels\Cyan::class, Channels\Magenta::class, Channels\Yellow::class, Channels\Key::class ]; /** * {@inheritdoc} * * @see ColorspaceInterface::createColor() */ public function colorFromNormalized(array $normalized): ColorInterface { return new Color(...array_map( fn(string $classname, float $value_normalized) => (new $classname(normalized: $value_normalized))->value(), self::$channels, $normalized, )); } /** * @throws ColorException */ public function importColor(ColorInterface $color): ColorInterface { return match ($color::class) { RgbColor::class => $this->importRgbColor($color), HsvColor::class => $this->importRgbColor($color->convertTo(RgbColorspace::class)), HslColor::class => $this->importRgbColor($color->convertTo(RgbColorspace::class)), default => $color, }; } /** * @throws ColorException */ protected function importRgbColor(ColorInterface $color): CmykColor { if (!($color instanceof RgbColor)) { throw new ColorException('Unabled to import color of type ' . $color::class . '.'); } $c = (255 - $color->red()->value()) / 255.0 * 100; $m = (255 - $color->green()->value()) / 255.0 * 100; $y = (255 - $color->blue()->value()) / 255.0 * 100; $k = intval(round(min([$c, $m, $y]))); $c = intval(round($c - $k)); $m = intval(round($m - $k)); $y = intval(round($y - $k)); return new CmykColor($c, $m, $y, $k); } } ================================================ FILE: src/Colors/Cmyk/Decoders/StringColorDecoder.php ================================================ [0-9\.]+%?), ?(?P[0-9\.]+%?), ?(?P[0-9\.]+%?), ?(?P[0-9\.]+%?)\)$/i'; if (preg_match($pattern, $input, $matches) != 1) { throw new DecoderException('Unable to decode input'); } $values = array_map(function (string $value): int { return intval(round(floatval(trim(str_replace('%', '', $value))))); }, [$matches['c'], $matches['m'], $matches['y'], $matches['k']]); return new Color(...$values); } } ================================================ FILE: src/Colors/Hsl/Channels/Hue.php ================================================ channels = [ new Hue($h), new Saturation($s), new Luminance($l), ]; } /** * {@inheritdoc} * * @see ColorInterface::colorspace() */ public function colorspace(): ColorspaceInterface { return new Colorspace(); } /** * {@inheritdoc} * * @see ColorInterface::create() */ public static function create(mixed $input): ColorInterface { return InputHandler::withDecoders([ Decoders\StringColorDecoder::class, ])->handle($input); } /** * Return the Hue channel */ public function hue(): ColorChannelInterface { /** @throws void */ return $this->channel(Hue::class); } /** * Return the Saturation channel */ public function saturation(): ColorChannelInterface { /** @throws void */ return $this->channel(Saturation::class); } /** * Return the Luminance channel */ public function luminance(): ColorChannelInterface { /** @throws void */ return $this->channel(Luminance::class); } public function toHex(string $prefix = ''): string { return $this->convertTo(RgbColorspace::class)->toHex($prefix); } /** * {@inheritdoc} * * @see ColorInterface::toString() */ public function toString(): string { return sprintf( 'hsl(%d, %d%%, %d%%)', $this->hue()->value(), $this->saturation()->value(), $this->luminance()->value() ); } /** * {@inheritdoc} * * @see ColorInterface::isGreyscale() */ public function isGreyscale(): bool { return $this->saturation()->value() == 0; } /** * {@inheritdoc} * * @see ColorInterface::isTransparent() */ public function isTransparent(): bool { return false; } /** * {@inheritdoc} * * @see ColorInterface::isClear() */ public function isClear(): bool { return false; } } ================================================ FILE: src/Colors/Hsl/Colorspace.php ================================================ */ public static array $channels = [ Channels\Hue::class, Channels\Saturation::class, Channels\Luminance::class ]; /** * {@inheritdoc} * * @see ColorspaceInterface::colorFromNormalized() */ public function colorFromNormalized(array $normalized): ColorInterface { return new Color(...array_map( fn(string $classname, float $value_normalized) => (new $classname(normalized: $value_normalized))->value(), self::$channels, $normalized )); } /** * @throws ColorException */ public function importColor(ColorInterface $color): ColorInterface { return match ($color::class) { CmykColor::class => $this->importRgbColor($color->convertTo(RgbColorspace::class)), RgbColor::class => $this->importRgbColor($color), HsvColor::class => $this->importHsvColor($color), default => $color, }; } /** * @throws ColorException */ protected function importRgbColor(ColorInterface $color): ColorInterface { if (!($color instanceof RgbColor)) { throw new ColorException('Unabled to import color of type ' . $color::class . '.'); } // normalized values of rgb channels $values = array_map( fn(ColorChannelInterface $channel): float => $channel->normalize(), $color->channels(), ); // take only RGB $values = array_slice($values, 0, 3); // calculate Luminance $min = min(...$values); $max = max(...$values); $luminance = ($max + $min) / 2; $delta = $max - $min; // calculate saturation $saturation = match (true) { $delta == 0 => 0, default => $delta / (1 - abs(2 * $luminance - 1)), }; // calculate hue [$r, $g, $b] = $values; $hue = match (true) { ($delta == 0) => 0, ($max == $r) => 60 * fmod((($g - $b) / $delta), 6), ($max == $g) => 60 * ((($b - $r) / $delta) + 2), ($max == $b) => 60 * ((($r - $g) / $delta) + 4), default => 0, }; $hue = ($hue + 360) % 360; // normalize hue return new Color( intval(round($hue)), intval(round($saturation * 100)), intval(round($luminance * 100)), ); } /** * @throws ColorException */ protected function importHsvColor(ColorInterface $color): ColorInterface { if (!($color instanceof HsvColor)) { throw new ColorException('Unabled to import color of type ' . $color::class . '.'); } // normalized values of hsv channels [$h, $s, $v] = array_map( fn(ColorChannelInterface $channel): float => $channel->normalize(), $color->channels(), ); // calculate Luminance $luminance = (2 - $s) * $v / 2; // calculate Saturation $saturation = match (true) { $luminance == 0 => $s, $luminance == 1 => 0, $luminance < .5 => $s * $v / ($luminance * 2), default => $s * $v / (2 - $luminance * 2), }; return new Color( intval(round($h * 360)), intval(round($saturation * 100)), intval(round($luminance * 100)), ); } } ================================================ FILE: src/Colors/Hsl/Decoders/StringColorDecoder.php ================================================ [0-9\.]+), ?(?P[0-9\.]+%?), ?(?P[0-9\.]+%?)\)$/i'; if (preg_match($pattern, $input, $matches) != 1) { throw new DecoderException('Unable to decode input'); } $values = array_map(function (string $value): int { return match (strpos($value, '%')) { false => intval(trim($value)), default => intval(trim(str_replace('%', '', $value))), }; }, [$matches['h'], $matches['s'], $matches['l']]); return new Color(...$values); } } ================================================ FILE: src/Colors/Hsv/Channels/Hue.php ================================================ channels = [ new Hue($h), new Saturation($s), new Value($v), ]; } /** * {@inheritdoc} * * @see ColorInterface::colorspace() */ public function colorspace(): ColorspaceInterface { return new Colorspace(); } /** * {@inheritdoc} * * @see ColorInterface::create() */ public static function create(mixed $input): ColorInterface { return InputHandler::withDecoders([ Decoders\StringColorDecoder::class, ])->handle($input); } /** * Return the Hue channel */ public function hue(): ColorChannelInterface { /** @throws void */ return $this->channel(Hue::class); } /** * Return the Saturation channel */ public function saturation(): ColorChannelInterface { /** @throws void */ return $this->channel(Saturation::class); } /** * Return the Value channel */ public function value(): ColorChannelInterface { /** @throws void */ return $this->channel(Value::class); } public function toHex(string $prefix = ''): string { return $this->convertTo(RgbColorspace::class)->toHex($prefix); } /** * {@inheritdoc} * * @see ColorInterface::toString() */ public function toString(): string { return sprintf( 'hsv(%d, %d%%, %d%%)', $this->hue()->value(), $this->saturation()->value(), $this->value()->value() ); } /** * {@inheritdoc} * * @see ColorInterface::isGreyscale() */ public function isGreyscale(): bool { return $this->saturation()->value() == 0; } /** * {@inheritdoc} * * @see ColorInterface::isTransparent() */ public function isTransparent(): bool { return false; } /** * {@inheritdoc} * * @see ColorInterface::isClear() */ public function isClear(): bool { return false; } } ================================================ FILE: src/Colors/Hsv/Colorspace.php ================================================ */ public static array $channels = [ Channels\Hue::class, Channels\Saturation::class, Channels\Value::class ]; /** * {@inheritdoc} * * @see ColorspaceInterface::colorFromNormalized() */ public function colorFromNormalized(array $normalized): ColorInterface { return new Color(...array_map( fn(string $classname, float $value_normalized) => (new $classname(normalized: $value_normalized))->value(), self::$channels, $normalized )); } /** * @throws ColorException */ public function importColor(ColorInterface $color): ColorInterface { return match ($color::class) { CmykColor::class => $this->importRgbColor($color->convertTo(RgbColorspace::class)), RgbColor::class => $this->importRgbColor($color), HslColor::class => $this->importHslColor($color), default => $color, }; } /** * @throws ColorException */ protected function importRgbColor(ColorInterface $color): ColorInterface { if (!($color instanceof RgbColor)) { throw new ColorException('Unabled to import color of type ' . $color::class . '.'); } // normalized values of rgb channels $values = array_map(fn(ColorChannelInterface $channel): float => $channel->normalize(), $color->channels()); // take only RGB $values = array_slice($values, 0, 3); // calculate chroma $min = min(...$values); $max = max(...$values); $chroma = $max - $min; // calculate value $v = 100 * $max; if ($chroma == 0) { // greyscale color return new Color(0, 0, intval(round($v))); } // calculate saturation $s = 100 * ($chroma / $max); // calculate hue [$r, $g, $b] = $values; $h = match (true) { ($r == $min) => 3 - (($g - $b) / $chroma), ($b == $min) => 1 - (($r - $g) / $chroma), default => 5 - (($b - $r) / $chroma), } * 60; return new Color( intval(round($h)), intval(round($s)), intval(round($v)) ); } /** * @throws ColorException */ protected function importHslColor(ColorInterface $color): ColorInterface { if (!($color instanceof HslColor)) { throw new ColorException('Unabled to import color of type ' . $color::class . '.'); } // normalized values of hsl channels [$h, $s, $l] = array_map( fn(ColorChannelInterface $channel): float => $channel->normalize(), $color->channels() ); $v = $l + $s * min($l, 1 - $l); $s = ($v == 0) ? 0 : 2 * (1 - $l / $v); return $this->colorFromNormalized([$h, $s, $v]); } } ================================================ FILE: src/Colors/Hsv/Decoders/StringColorDecoder.php ================================================ [0-9\.]+), ?(?P[0-9\.]+%?), ?(?P[0-9\.]+%?)\)$/i'; if (preg_match($pattern, $input, $matches) != 1) { throw new DecoderException('Unable to decode input'); } $values = array_map(function (string $value): int { return match (strpos($value, '%')) { false => intval(trim($value)), default => intval(trim(str_replace('%', '', $value))), }; }, [$matches['h'], $matches['s'], $matches['v']]); return new Color(...$values); } } ================================================ FILE: src/Colors/Profile.php ================================================ normalize(), 6)); } } ================================================ FILE: src/Colors/Rgb/Channels/Blue.php ================================================ channels = [ new Red($r), new Green($g), new Blue($b), new Alpha($a), ]; } /** * {@inheritdoc} * * @see ColorInterface::colorspace() */ public function colorspace(): ColorspaceInterface { return new Colorspace(); } /** * {@inheritdoc} * * @see ColorInterface::create() */ public static function create(mixed $input): ColorInterface { return InputHandler::withDecoders([ Decoders\HexColorDecoder::class, Decoders\StringColorDecoder::class, Decoders\TransparentColorDecoder::class, Decoders\HtmlColornameDecoder::class, ])->handle($input); } /** * Return the RGB red color channel */ public function red(): ColorChannelInterface { /** @throws void */ return $this->channel(Red::class); } /** * Return the RGB green color channel */ public function green(): ColorChannelInterface { /** @throws void */ return $this->channel(Green::class); } /** * Return the RGB blue color channel */ public function blue(): ColorChannelInterface { /** @throws void */ return $this->channel(Blue::class); } /** * Return the colors alpha channel */ public function alpha(): ColorChannelInterface { /** @throws void */ return $this->channel(Alpha::class); } /** * {@inheritdoc} * * @see ColorInterface::toHex() */ public function toHex(string $prefix = ''): string { if ($this->isTransparent()) { return sprintf( '%s%02x%02x%02x%02x', $prefix, $this->red()->value(), $this->green()->value(), $this->blue()->value(), $this->alpha()->value() ); } return sprintf( '%s%02x%02x%02x', $prefix, $this->red()->value(), $this->green()->value(), $this->blue()->value() ); } /** * {@inheritdoc} * * @see ColorInterface::toString() */ public function toString(): string { if ($this->isTransparent()) { return sprintf( 'rgba(%d, %d, %d, %.1F)', $this->red()->value(), $this->green()->value(), $this->blue()->value(), $this->alpha()->normalize(), ); } return sprintf( 'rgb(%d, %d, %d)', $this->red()->value(), $this->green()->value(), $this->blue()->value() ); } /** * {@inheritdoc} * * @see ColorInterface::isGreyscale() */ public function isGreyscale(): bool { $values = [$this->red()->value(), $this->green()->value(), $this->blue()->value()]; return count(array_unique($values, SORT_REGULAR)) === 1; } /** * {@inheritdoc} * * @see ColorInterface::isTransparent() */ public function isTransparent(): bool { return $this->alpha()->value() < $this->alpha()->max(); } /** * {@inheritdoc} * * @see ColorInterface::isClear() */ public function isClear(): bool { return $this->alpha()->value() == 0; } } ================================================ FILE: src/Colors/Rgb/Colorspace.php ================================================ */ public static array $channels = [ Channels\Red::class, Channels\Green::class, Channels\Blue::class, Channels\Alpha::class ]; /** * {@inheritdoc} * * @see ColorspaceInterface::colorFromNormalized() */ public function colorFromNormalized(array $normalized): ColorInterface { return new Color(...array_map( fn($classname, float $value_normalized) => (new $classname(normalized: $value_normalized))->value(), self::$channels, $normalized, )); } /** * @throws ColorException */ public function importColor(ColorInterface $color): ColorInterface { return match ($color::class) { CmykColor::class => $this->importCmykColor($color), HsvColor::class => $this->importHsvColor($color), HslColor::class => $this->importHslColor($color), default => $color, }; } /** * @throws ColorException */ protected function importCmykColor(ColorInterface $color): ColorInterface { if (!($color instanceof CmykColor)) { throw new ColorException('Unabled to import color of type ' . $color::class . '.'); } return new Color( (int) (255 * (1 - $color->cyan()->normalize()) * (1 - $color->key()->normalize())), (int) (255 * (1 - $color->magenta()->normalize()) * (1 - $color->key()->normalize())), (int) (255 * (1 - $color->yellow()->normalize()) * (1 - $color->key()->normalize())), ); } /** * @throws ColorException */ protected function importHsvColor(ColorInterface $color): ColorInterface { if (!($color instanceof HsvColor)) { throw new ColorException('Unabled to import color of type ' . $color::class . '.'); } $chroma = $color->value()->normalize() * $color->saturation()->normalize(); $hue = $color->hue()->normalize() * 6; $x = $chroma * (1 - abs(fmod($hue, 2) - 1)); // connect channel values $values = match (true) { $hue < 1 => [$chroma, $x, 0], $hue < 2 => [$x, $chroma, 0], $hue < 3 => [0, $chroma, $x], $hue < 4 => [0, $x, $chroma], $hue < 5 => [$x, 0, $chroma], default => [$chroma, 0, $x], }; // add to each value $values = array_map(fn(float|int $value): float => $value + $color->value()->normalize() - $chroma, $values); $values[] = 1; // append alpha channel value return $this->colorFromNormalized($values); } /** * @throws ColorException */ protected function importHslColor(ColorInterface $color): ColorInterface { if (!($color instanceof HslColor)) { throw new ColorException('Unabled to import color of type ' . $color::class . '.'); } // normalized values of hsl channels [$h, $s, $l] = array_map( fn(ColorChannelInterface $channel): float => $channel->normalize(), $color->channels() ); $c = (1 - abs(2 * $l - 1)) * $s; $x = $c * (1 - abs(fmod($h * 6, 2) - 1)); $m = $l - $c / 2; $values = match (true) { $h < 1 / 6 => [$c, $x, 0], $h < 2 / 6 => [$x, $c, 0], $h < 3 / 6 => [0, $c, $x], $h < 4 / 6 => [0, $x, $c], $h < 5 / 6 => [$x, 0, $c], default => [$c, 0, $x], }; $values = array_map(fn(float|int $value): float => $value + $m, $values); $values[] = 1; // append alpha channel value return $this->colorFromNormalized($values); } } ================================================ FILE: src/Colors/Rgb/Decoders/HexColorDecoder.php ================================================ [a-f\d]{3}(?:[a-f\d]?|(?:[a-f\d]{3}(?:[a-f\d]{2})?)?)\b)$/i'; if (preg_match($pattern, $input, $matches) != 1) { throw new DecoderException('Unable to decode input'); } $values = match (strlen($matches['hex'])) { 3, 4 => str_split($matches['hex']), 6, 8 => str_split($matches['hex'], 2), default => throw new DecoderException('Unable to decode input'), }; $values = array_map(function (string $value): float|int { return match (strlen($value)) { 1 => hexdec($value . $value), 2 => hexdec($value), default => throw new DecoderException('Unable to decode input'), }; }, $values); return new Color(...$values); } } ================================================ FILE: src/Colors/Rgb/Decoders/HtmlColornameDecoder.php ================================================ */ protected static array $names = [ 'lightsalmon' => '#ffa07a', 'salmon' => '#fa8072', 'darksalmon' => '#e9967a', 'lightcoral' => '#f08080', 'indianred' => '#cd5c5c', 'crimson' => '#dc143c', 'firebrick' => '#b22222', 'red' => '#ff0000', 'darkred' => '#8b0000', 'coral' => '#ff7f50', 'tomato' => '#ff6347', 'orangered' => '#ff4500', 'gold' => '#ffd700', 'orange' => '#ffa500', 'darkorange' => '#ff8c00', 'lightyellow' => '#ffffe0', 'lemonchiffon' => '#fffacd', 'lightgoldenrodyellow' => '#fafad2', 'papayawhip' => '#ffefd5', 'moccasin' => '#ffe4b5', 'peachpuff' => '#ffdab9', 'palegoldenrod' => '#eee8aa', 'khaki' => '#f0e68c', 'darkkhaki' => '#bdb76b', 'yellow' => '#ffff00', 'lawngreen' => '#7cfc00', 'chartreuse' => '#7fff00', 'limegreen' => '#32cd32', 'lime' => '#00ff00', 'forestgreen' => '#228b22', 'green' => '#008000', 'darkgreen' => '#006400', 'greenyellow' => '#adff2f', 'yellowgreen' => '#9acd32', 'springgreen' => '#00ff7f', 'mediumspringgreen' => '#00fa9a', 'lightgreen' => '#90ee90', 'palegreen' => '#98fb98', 'darkseagreen' => '#8fbc8f', 'mediumseagre' => 'en #3cb371', 'seagreen' => '#2e8b57', 'olive' => '#808000', 'darkolivegreen' => '#556b2f', 'olivedrab' => '#6b8e23', 'lightcyan' => '#e0ffff', 'cyan' => '#00ffff', 'aqua' => '#00ffff', 'aquamarine' => '#7fffd4', 'mediumaquamarine' => '#66cdaa', 'paleturquoise' => '#afeeee', 'turquoise' => '#40e0d0', 'mediumturquoise' => '#48d1cc', 'darkturquoise' => '#00ced1', 'lightseagreen' => '#20b2aa', 'cadetblue' => '#5f9ea0', 'darkcyan' => '#008b8b', 'teal' => '#008080', 'powderblue' => '#b0e0e6', 'lightblue' => '#add8e6', 'lightskyblue' => '#87cefa', 'skyblue' => '#87ceeb', 'deepskyblue' => '#00bfff', 'lightsteelblue' => '#b0c4de', 'dodgerblue' => '#1e90ff', 'cornflowerblue' => '#6495ed', 'steelblue' => '#4682b4', 'royalblue' => '#4169e1', 'blue' => '#0000ff', 'mediumblue' => '#0000cd', 'darkblue' => '#00008b', 'navy' => '#000080', 'midnightblue' => '#191970', 'mediumslateblue' => '#7b68ee', 'slateblue' => '#6a5acd', 'darkslateblue' => '#483d8b', 'lavender' => '#e6e6fa', 'thistle' => '#d8bfd8', 'plum' => '#dda0dd', 'violet' => '#ee82ee', 'orchid' => '#da70d6', 'fuchsia' => '#ff00ff', 'magenta' => '#ff00ff', 'mediumorchid' => '#ba55d3', 'mediumpurple' => '#9370db', 'blueviolet' => '#8a2be2', 'darkviolet' => '#9400d3', 'darkorchid' => '#9932cc', 'darkmagenta' => '#8b008b', 'purple' => '#800080', 'indigo' => '#4b0082', 'pink' => '#ffc0cb', 'lightpink' => '#ffb6c1', 'hotpink' => '#ff69b4', 'deeppink' => '#ff1493', 'palevioletred' => '#db7093', 'mediumvioletred' => '#c71585', 'white' => '#ffffff', 'snow' => '#fffafa', 'honeydew' => '#f0fff0', 'mintcream' => '#f5fffa', 'azure' => '#f0ffff', 'aliceblue' => '#f0f8ff', 'ghostwhite' => '#f8f8ff', 'whitesmoke' => '#f5f5f5', 'seashell' => '#fff5ee', 'beige' => '#f5f5dc', 'oldlace' => '#fdf5e6', 'floralwhite' => '#fffaf0', 'ivory' => '#fffff0', 'antiquewhite' => '#faebd7', 'linen' => '#faf0e6', 'lavenderblush' => '#fff0f5', 'mistyrose' => '#ffe4e1', 'gainsboro' => '#dcdcdc', 'lightgray' => '#d3d3d3', 'silver' => '#c0c0c0', 'darkgray' => '#a9a9a9', 'gray' => '#808080', 'dimgray' => '#696969', 'lightslategray' => '#778899', 'slategray' => '#708090', 'darkslategray' => '#2f4f4f', 'black' => '#000000', 'cornsilk' => '#fff8dc', 'blanchedalmond' => '#ffebcd', 'bisque' => '#ffe4c4', 'navajowhite' => '#ffdead', 'wheat' => '#f5deb3', 'burlywood' => '#deb887', 'tan' => '#d2b48c', 'rosybrown' => '#bc8f8f', 'sandybrown' => '#f4a460', 'goldenrod' => '#daa520', 'peru' => '#cd853f', 'chocolate' => '#d2691e', 'saddlebrown' => '#8b4513', 'sienna' => '#a0522d', 'brown' => '#a52a2a', 'maroon' => '#800000', ]; /** * Decode html color names */ public function decode(mixed $input): ImageInterface|ColorInterface { if (!is_string($input)) { throw new DecoderException('Unable to decode input'); } if (!array_key_exists(strtolower($input), static::$names)) { throw new DecoderException('Unable to decode input'); } return parent::decode(static::$names[strtolower($input)]); } } ================================================ FILE: src/Colors/Rgb/Decoders/StringColorDecoder.php ================================================ [0-9\.]+%?), ?(?P[0-9\.]+%?), ?(?P[0-9\.]+%?)' . '(?:, ?(?P(?:1)|(?:1\.0*)|(?:0)|(?:0?\.\d+%?)|(?:\d{1,3}%)))?\)$/i'; if (preg_match($pattern, $input, $matches) != 1) { throw new DecoderException('Unable to decode input'); } // rgb values $values = array_map(function (string $value): int { return match (strpos($value, '%')) { false => intval(trim($value)), default => intval(round(floatval(trim(str_replace('%', '', $value))) / 100 * 255)), }; }, [$matches['r'], $matches['g'], $matches['b']]); // alpha value if (array_key_exists('a', $matches)) { $values[] = match (true) { strpos($matches['a'], '%') => round(intval(trim(str_replace('%', '', $matches['a']))) / 2.55), default => intval(round(floatval(trim($matches['a'])) * 255)), }; } return new Color(...$values); } } ================================================ FILE: src/Colors/Rgb/Decoders/TransparentColorDecoder.php ================================================ prepareOptions($options) as $name => $value) { if (!property_exists($this, $name)) { throw new InputException('Property ' . $name . ' does not exists for ' . $this::class . '.'); } $this->{$name} = $value; } return $this; } /** * This method makes it possible to call self::setOptions() with a single * array instead of named parameters * * @param array $options * @return array */ private function prepareOptions(array $options): array { if ($options === []) { return $options; } if (count($options) > 1) { return $options; } if (!array_key_exists(0, $options)) { return $options; } if (!is_array($options[0])) { return $options; } return $options[0]; } } ================================================ FILE: src/Decoders/Base64ImageDecoder.php ================================================ PHP_MAXPATHLEN) { return false; } try { if (!@is_file($input)) { return false; } } catch (Exception) { return false; } return true; } /** * Extract and return EXIF data from given input which can be binary image * data or a file path. * * @return CollectionInterface */ protected function extractExifData(string $path_or_data): CollectionInterface { if (!function_exists('exif_read_data')) { return new Collection(); } try { $source = match (true) { $this->isFile($path_or_data) => $path_or_data, // path default => $this->buildFilePointer($path_or_data), // data }; // extract exif data $data = @exif_read_data($source, null, true); if (is_resource($source)) { fclose($source); } } catch (Exception) { $data = []; } return new Collection(is_array($data) ? $data : []); } /** * Determine if given input is base64 encoded data */ protected function isValidBase64(mixed $input): bool { if (!is_string($input)) { return false; } return base64_encode(base64_decode($input)) === str_replace(["\n", "\r"], '', $input); } /** * Parse data uri */ protected function parseDataUri(mixed $input): object { $pattern = "/^data:(?P\w+\/[-+.\w]+)?" . "(?P(;[-\w]+=[-\w]+)*)(?P;base64)?,(?P.*)/"; $result = preg_match($pattern, (string) $input, $matches); return new class ($matches, $result) { /** * @param array $matches * @return void */ public function __construct(private array $matches, private int|false $result) { // } public function isValid(): bool { return (bool) $this->result; } public function mediaType(): ?string { if (isset($this->matches['mediatype']) && !empty($this->matches['mediatype'])) { return $this->matches['mediatype']; } return null; } public function hasMediaType(): bool { return !empty($this->mediaType()); } public function isBase64Encoded(): bool { return isset($this->matches['base64']) && $this->matches['base64'] === ';base64'; } public function data(): ?string { if (isset($this->matches['data']) && !empty($this->matches['data'])) { return $this->matches['data']; } return null; } }; } } ================================================ FILE: src/Drivers/AbstractDriver.php ================================================ config = new Config(); $this->checkHealth(); } /** * {@inheritdoc} * * @see DriverInterface::config() */ public function config(): Config { return $this->config; } /** * {@inheritdoc} * * @see DriverInterface::handleInput() */ public function handleInput(mixed $input, array $decoders = []): ImageInterface|ColorInterface { return InputHandler::withDecoders($decoders, $this)->handle($input); } /** * {@inheritdoc} * * @see DriverInterface::specialize() */ public function specialize( ModifierInterface|AnalyzerInterface|EncoderInterface|DecoderInterface $object ): ModifierInterface|AnalyzerInterface|EncoderInterface|DecoderInterface { // return object directly if no specializing is possible if (!($object instanceof SpecializableInterface)) { return $object; } // return directly and only attach driver if object is already specialized if ($object instanceof SpecializedInterface) { $object->setDriver($this); return $object; } // resolve classname for specializable object $specialized_classname = implode("\\", [ (new ReflectionClass($this))->getNamespaceName(), // driver's namespace match (true) { $object instanceof ModifierInterface => 'Modifiers', $object instanceof AnalyzerInterface => 'Analyzers', $object instanceof EncoderInterface => 'Encoders', $object instanceof DecoderInterface => 'Decoders', }, $object_shortname = (new ReflectionClass($object))->getShortName(), ]); // fail if driver specialized classname does not exists if (!class_exists($specialized_classname)) { throw new NotSupportedException( "Class '" . $object_shortname . "' is not supported by " . $this->id() . " driver." ); } // create a driver specialized object with the specializable properties of generic object $specialized = new $specialized_classname(...$object->specializable()); // attach driver return $specialized->setDriver($this); } /** * {@inheritdoc} * * @see DriverInterface::specializeMultiple() * * @throws NotSupportedException * @throws DriverException */ public function specializeMultiple(array $objects): array { return array_map( function (string|object $object): ModifierInterface|AnalyzerInterface|EncoderInterface|DecoderInterface { return $this->specialize( match (true) { is_string($object) => new $object(), is_object($object) => $object, } ); }, $objects ); } } ================================================ FILE: src/Drivers/AbstractEncoder.php ================================================ encode($this); } /** * Build new file pointer, run callback with it and return result as encoded image * * @throws RuntimeException */ protected function createEncodedImage(callable $callback, ?string $mediaType = null): EncodedImage { $pointer = $this->buildFilePointer(); $callback($pointer); return is_string($mediaType) ? new EncodedImage($pointer, $mediaType) : new EncodedImage($pointer); } } ================================================ FILE: src/Drivers/AbstractFontProcessor.php ================================================ wrapTextBlock(new TextBlock($text), $font); $pivot = $this->buildPivot($lines, $font, $position); $leading = $this->leading($font); $blockWidth = $this->boxSize((string) $lines->longestLine(), $font)->width(); $x = $pivot->x(); $y = $font->hasFilename() ? $pivot->y() + $this->capHeight($font) : $pivot->y(); $xAdjustment = 0; // adjust line positions according to alignment foreach ($lines as $line) { $lineBoxSize = $this->boxSize((string) $line, $font); $lineWidth = $lineBoxSize->width() + $lineBoxSize->pivot()->x(); $xAdjustment = $font->alignment() === 'left' ? 0 : $blockWidth - $lineWidth; $xAdjustment = $font->alignment() === 'right' ? intval(round($xAdjustment)) : $xAdjustment; $xAdjustment = $font->alignment() === 'center' ? intval(round($xAdjustment / 2)) : $xAdjustment; $position = new Point($x + $xAdjustment, $y); $position->rotate($font->angle(), $pivot); $line->setPosition($position); $y += $leading; } return $lines; } /** * {@inheritdoc} * * @see FontProcessorInterface::nativeFontSize() */ public function nativeFontSize(FontInterface $font): float { return $font->size(); } /** * {@inheritdoc} * * @see FontProcessorInterface::typographicalSize() */ public function typographicalSize(FontInterface $font): int { return $this->boxSize('Hy', $font)->height(); } /** * {@inheritdoc} * * @see FontProcessorInterface::capHeight() */ public function capHeight(FontInterface $font): int { return $this->boxSize('T', $font)->height(); } /** * {@inheritdoc} * * @see FontProcessorInterface::leading() */ public function leading(FontInterface $font): int { return intval(round($this->typographicalSize($font) * $font->lineHeight())); } /** * Reformat a text block by wrapping each line before the given maximum width * * @throws FontException */ protected function wrapTextBlock(TextBlock $block, FontInterface $font): TextBlock { $newLines = []; foreach ($block as $line) { foreach ($this->wrapLine($line, $font) as $newLine) { $newLines[] = $newLine; } } return $block->setLines($newLines); } /** * Check if a line exceeds the given maximum width and wrap it if necessary. * The output will be an array of formatted lines that are all within the * maximum width. * * @throws FontException * @return array */ protected function wrapLine(Line $line, FontInterface $font): array { // no wrap width - no wrapping if (is_null($font->wrapWidth())) { return [$line]; } $wrapped = []; $formattedLine = new Line(); foreach ($line as $word) { // calculate width of newly formatted line $lineWidth = $this->boxSize(match ($formattedLine->count()) { 0 => $word, default => $formattedLine . ' ' . $word, }, $font)->width(); // decide if word fits on current line or a new line must be created if ($line->count() === 1 || $lineWidth <= $font->wrapWidth()) { $formattedLine->add($word); } else { if ($formattedLine->count() !== 0) { $wrapped[] = $formattedLine; } $formattedLine = new Line($word); } } $wrapped[] = $formattedLine; return $wrapped; } /** * Build pivot point of textblock according to the font settings and based on given position * * @throws FontException */ protected function buildPivot(TextBlock $block, FontInterface $font, PointInterface $position): PointInterface { // bounding box $box = new Rectangle( $this->boxSize((string) $block->longestLine(), $font)->width(), $this->leading($font) * ($block->count() - 1) + $this->capHeight($font) ); // set position $box->setPivot($position); // alignment $box->align($font->alignment()); $box->valign($font->valignment()); $box->rotate($font->angle()); return $box->last(); } } ================================================ FILE: src/Drivers/AbstractFrame.php ================================================ */ public function __debugInfo(): array { return [ 'delay' => $this->delay(), 'left' => $this->offsetLeft(), 'top' => $this->offsetTop(), 'dispose' => $this->dispose(), ]; } } ================================================ FILE: src/Drivers/Gd/Analyzers/ColorspaceAnalyzer.php ================================================ core()->native()); } } ================================================ FILE: src/Drivers/Gd/Analyzers/PixelColorAnalyzer.php ================================================ colorAt( $image->colorspace(), $image->core()->frame($this->frame_key)->native() ); } /** * @throws GeometryException * @throws ColorException */ protected function colorAt(ColorspaceInterface $colorspace, GdImage $gd): ColorInterface { $index = @imagecolorat($gd, $this->x, $this->y); if (!imageistruecolor($gd)) { $index = imagecolorsforindex($gd, $index); } if ($index === false) { throw new GeometryException( 'The specified position is not in the valid image area.' ); } return $this->driver()->colorProcessor($colorspace)->nativeToColor($index); } } ================================================ FILE: src/Drivers/Gd/Analyzers/PixelColorsAnalyzer.php ================================================ colorspace(); foreach ($image as $frame) { $colors->push( parent::colorAt($colorspace, $frame->native()) ); } return $colors; } } ================================================ FILE: src/Drivers/Gd/Analyzers/ResolutionAnalyzer.php ================================================ core()->native())); } } ================================================ FILE: src/Drivers/Gd/Analyzers/WidthAnalyzer.php ================================================ core()->native()); } } ================================================ FILE: src/Drivers/Gd/Cloner.php ================================================ width(), $size->height()); // copy resolution to clone $resolution = imageresolution($gd); if (is_array($resolution) && array_key_exists(0, $resolution) && array_key_exists(1, $resolution)) { imageresolution($clone, $resolution[0], $resolution[1]); } // fill with background $processor = new ColorProcessor(); imagefill($clone, 0, 0, $processor->colorToNative($background)); imagealphablending($clone, true); imagesavealpha($clone, true); // set background image as transparent if alpha channel value if color is below .5 // comes into effect when the end format only supports binary transparency (like GIF) if ($background->channel(Alpha::class)->value() < 128) { imagecolortransparent($clone, $processor->colorToNative($background)); } return $clone; } /** * Create a clone of an GdImage that is positioned on the specified background color. * Possible transparent areas are mixed with this color. * * @throws ColorException */ public static function cloneBlended(GdImage $gd, ColorInterface $background): GdImage { // create empty canvas with same size $clone = static::cloneEmpty($gd, background: $background); // transfer actual image to clone imagecopy($clone, $gd, 0, 0, 0, 0, imagesx($gd), imagesy($gd)); return $clone; } } ================================================ FILE: src/Drivers/Gd/ColorProcessor.php ================================================ convertTo($this->colorspace); // gd only supports rgb so the channels can be accessed directly $r = $color->channel(Red::class)->value(); $g = $color->channel(Green::class)->value(); $b = $color->channel(Blue::class)->value(); $a = $color->channel(Alpha::class)->value(); // convert alpha value to gd alpha // ([opaque]255-0[transparent]) to ([opaque]0-127[transparent]) $a = (int) $this->convertRange($a, 0, 255, 127, 0); return ($a << 24) + ($r << 16) + ($g << 8) + $b; } /** * {@inheritdoc} * * @see ColorProcessorInterface::nativeToColor() */ public function nativeToColor(mixed $value): ColorInterface { if (!is_int($value) && !is_array($value)) { throw new ColorException('GD driver can only decode colors in integer and array format.'); } if (is_array($value)) { // array conversion if (!$this->isValidArrayColor($value)) { throw new ColorException( 'GD driver can only decode array color format array{red: int, green: int, blue: int, alpha: int}.', ); } $r = $value['red']; $g = $value['green']; $b = $value['blue']; $a = $value['alpha']; } else { // integer conversion $a = ($value >> 24) & 0xFF; $r = ($value >> 16) & 0xFF; $g = ($value >> 8) & 0xFF; $b = $value & 0xFF; } // convert gd apha integer to intervention alpha integer // ([opaque]0-127[transparent]) to ([opaque]255-0[transparent]) $a = (int) static::convertRange($a, 127, 0, 0, 255); return new Color($r, $g, $b, $a); } /** * Convert input in range (min) to (max) to the corresponding value * in target range (targetMin) to (targetMax). */ protected function convertRange( float|int $input, float|int $min, float|int $max, float|int $targetMin, float|int $targetMax ): float|int { return ceil(((($input - $min) * ($targetMax - $targetMin)) / ($max - $min)) + $targetMin); } /** * Check if given array is valid color format * array{red: int, green: int, blue: int, alpha: int} * i.e. result of imagecolorsforindex() * * @param array $color */ private function isValidArrayColor(array $color): bool { if (!array_key_exists('red', $color)) { return false; } if (!array_key_exists('green', $color)) { return false; } if (!array_key_exists('blue', $color)) { return false; } if (!array_key_exists('alpha', $color)) { return false; } if (!is_int($color['red'])) { return false; } if (!is_int($color['green'])) { return false; } if (!is_int($color['blue'])) { return false; } if (!is_int($color['alpha'])) { return false; } return true; } } ================================================ FILE: src/Drivers/Gd/Core.php ================================================ push($frame); return $this; } /** * {@inheritdoc} * * @see CoreInterface::native() */ public function native(): mixed { return $this->first()->native(); } /** * {@inheritdoc} * * @see CoreInterface::setNative() */ public function setNative(mixed $native): self { $this->empty()->push(new Frame($native)); return $this; } /** * {@inheritdoc} * * @see CoreInterface::frame() */ public function frame(int $position): FrameInterface { $frame = $this->getAtPosition($position); if (!($frame instanceof FrameInterface)) { throw new AnimationException('Frame #' . $position . ' could not be found in the image.'); } return $frame; } /** * {@inheritdoc} * * @see CoreInterface::loops() */ public function loops(): int { return $this->loops; } /** * {@inheritdoc} * * @see CoreInterface::setLoops() */ public function setLoops(int $loops): self { $this->loops = $loops; return $this; } /** * {@inheritdoc} * * @see CollectionInterface::first() */ public function first(): FrameInterface { return parent::first(); } /** * {@inheritdoc} * * @see CollectionInterface::last() */ public function last(): FrameInterface { return parent::last(); } /** * Clone instance */ public function __clone(): void { foreach ($this->items as $key => $frame) { $this->items[$key] = clone $frame; } } } ================================================ FILE: src/Drivers/Gd/Decoders/AbstractDecoder.php ================================================ isValidBase64($input)) { throw new DecoderException('Unable to decode input'); } return parent::decode(base64_decode((string) $input)); } } ================================================ FILE: src/Drivers/Gd/Decoders/BinaryImageDecoder.php ================================================ isGifFormat($input)) { true => $this->decodeGif($input), default => $this->decodeBinary($input), }; } /** * Decode image from given binary data * * @throws RuntimeException */ private function decodeBinary(string $input): ImageInterface { $gd = @imagecreatefromstring($input); if ($gd === false) { throw new DecoderException('Unable to decode input'); } // create image instance $image = parent::decode($gd); // get media type $mediaType = $this->getMediaTypeByBinary($input); // extract & set exif data for appropriate formats if (in_array($mediaType->format(), [Format::JPEG, Format::TIFF])) { $image->setExif($this->extractExifData($input)); } // set mediaType on origin $image->origin()->setMediaType($mediaType); // adjust image orientation if ($this->driver()->config()->autoOrientation) { $image->modify(new AlignRotationModifier()); } return $image; } } ================================================ FILE: src/Drivers/Gd/Decoders/DataUriImageDecoder.php ================================================ parseDataUri($input); if (!$uri->isValid()) { throw new DecoderException('Unable to decode input'); } if ($uri->isBase64Encoded()) { return parent::decode(base64_decode($uri->data())); } return parent::decode(urldecode($uri->data())); } } ================================================ FILE: src/Drivers/Gd/Decoders/EncodedImageObjectDecoder.php ================================================ toString()); } } ================================================ FILE: src/Drivers/Gd/Decoders/FilePathImageDecoder.php ================================================ isFile($input)) { throw new DecoderException('Unable to decode input'); } // detect media (mime) type $mediaType = $this->getMediaTypeByFilePath($input); $image = match ($mediaType->format()) { // gif files might be animated and therefore cannot // be handled by the standard GD decoder. Format::GIF => $this->decodeGif($input), default => parent::decode(match ($mediaType->format()) { Format::JPEG => @imagecreatefromjpeg($input), Format::WEBP => @imagecreatefromwebp($input), Format::PNG => @imagecreatefrompng($input), Format::AVIF => @imagecreatefromavif($input), Format::BMP => @imagecreatefrombmp($input), default => throw new DecoderException('Unable to decode input'), }), }; // set file path & mediaType on origin $image->origin()->setFilePath($input); $image->origin()->setMediaType($mediaType); // extract exif for the appropriate formats if ($mediaType->format() === Format::JPEG) { $image->setExif($this->extractExifData($input)); } // adjust image orientation if ($this->driver()->config()->autoOrientation) { $image->modify(new AlignRotationModifier()); } return $image; } } ================================================ FILE: src/Drivers/Gd/Decoders/FilePointerImageDecoder.php ================================================ driver(), new Core([ new Frame($input) ]) ); } /** * Decode image from given GIF source which can be either a file path or binary data * * Depending on the configuration, this is taken over by the native GD function * or, if animations are required, by our own extended decoder. * * @throws RuntimeException */ protected function decodeGif(mixed $input): ImageInterface { // create non-animated image depending on config if (!$this->driver()->config()->decodeAnimation) { $native = match (true) { $this->isGifFormat($input) => @imagecreatefromstring($input), default => @imagecreatefromgif($input), }; if ($native === false) { throw new DecoderException('Unable to decode input.'); } $image = self::decode($native); $image->origin()->setMediaType('image/gif'); return $image; } try { // create empty core $core = new Core(); $gif = GifDecoder::decode($input); $splitter = GifSplitter::create($gif)->split(); $delays = $splitter->getDelays(); // set loops on core if ($loops = $gif->getMainApplicationExtension()?->getLoops()) { $core->setLoops($loops); } // add GDImage instances to core foreach ($splitter->coalesceToResources() as $key => $native) { $core->push( new Frame($native, $delays[$key] / 100) ); } } catch (Exception $e) { throw new DecoderException($e->getMessage(), $e->getCode(), $e); } // create (possibly) animated image $image = new Image($this->driver(), $core); // set media type $image->origin()->setMediaType('image/gif'); return $image; } } ================================================ FILE: src/Drivers/Gd/Decoders/SplFileInfoImageDecoder.php ================================================ getRealPath()); } } ================================================ FILE: src/Drivers/Gd/Driver.php ================================================ core->add( $this->driver->handleInput($source)->core()->first()->setDelay($delay) ); return $this; } /** * @throws RuntimeException */ public function __invoke(): ImageInterface { return new Image( $this->driver, $this->core ); } }; $init($animation); return call_user_func($animation); } /** * {@inheritdoc} * * @see DriverInterface::colorProcessor() */ public function colorProcessor(ColorspaceInterface $colorspace): ColorProcessorInterface { return new ColorProcessor($colorspace); } /** * {@inheritdoc} * * @see DriverInterface::fontProcessor() */ public function fontProcessor(): FontProcessorInterface { return new FontProcessor(); } /** * {@inheritdoc} * * @see DriverInterface::supports() */ public function supports(string|Format|FileExtension|MediaType $identifier): bool { return match (Format::tryCreate($identifier)) { Format::JPEG => boolval(imagetypes() & IMG_JPEG), Format::WEBP => boolval(imagetypes() & IMG_WEBP), Format::GIF => boolval(imagetypes() & IMG_GIF), Format::PNG => boolval(imagetypes() & IMG_PNG), Format::AVIF => boolval(imagetypes() & IMG_AVIF), Format::BMP => boolval(imagetypes() & IMG_BMP), default => false, }; } /** * Return version of GD library */ public static function version(): string { return gd_info()['GD Version']; } } ================================================ FILE: src/Drivers/Gd/Encoders/AvifEncoder.php ================================================ createEncodedImage(function ($pointer) use ($image): void { imageavif($image->core()->native(), $pointer, $this->quality); }, 'image/avif'); } } ================================================ FILE: src/Drivers/Gd/Encoders/BmpEncoder.php ================================================ createEncodedImage(function ($pointer) use ($image): void { imagebmp($image->core()->native(), $pointer, false); }, 'image/bmp'); } } ================================================ FILE: src/Drivers/Gd/Encoders/GifEncoder.php ================================================ isAnimated()) { return $this->encodeAnimated($image); } $gd = Cloner::clone($image->core()->native()); return $this->createEncodedImage(function ($pointer) use ($gd): void { imageinterlace($gd, $this->interlaced); imagegif($gd, $pointer); }, 'image/gif'); } /** * @throws RuntimeException */ protected function encodeAnimated(ImageInterface $image): EncodedImage { try { $builder = GifBuilder::canvas( $image->width(), $image->height() ); foreach ($image as $frame) { $builder->addFrame( source: $this->encode($frame->toImage($image->driver()))->toFilePointer(), delay: $frame->delay(), interlaced: $this->interlaced ); } $builder->setLoops($image->loops()); return new EncodedImage($builder->encode(), 'image/gif'); } catch (Exception $e) { throw new EncoderException($e->getMessage(), $e->getCode(), $e); } } } ================================================ FILE: src/Drivers/Gd/Encoders/JpegEncoder.php ================================================ driver()->handleInput( $this->driver()->config()->blendingColor ); $output = Cloner::cloneBlended( $image->core()->native(), background: $blendingColor ); return $this->createEncodedImage(function ($pointer) use ($output): void { imageinterlace($output, $this->progressive); imagejpeg($output, $pointer, $this->quality); }, 'image/jpeg'); } } ================================================ FILE: src/Drivers/Gd/Encoders/PngEncoder.php ================================================ prepareOutput($image); return $this->createEncodedImage(function ($pointer) use ($output): void { imageinterlace($output, $this->interlaced); imagepng($output, $pointer, -1); }, 'image/png'); } /** * Prepare given image instance for PNG format output according to encoder settings * * @throws RuntimeException * @throws ColorException * @throws AnimationException */ private function prepareOutput(ImageInterface $image): GdImage { if ($this->indexed) { $output = clone $image; $output->reduceColors(255); return $output->core()->native(); } return Cloner::clone($image->core()->native()); } } ================================================ FILE: src/Drivers/Gd/Encoders/WebpEncoder.php ================================================ quality === 100 && defined('IMG_WEBP_LOSSLESS') ? IMG_WEBP_LOSSLESS : $this->quality; return $this->createEncodedImage(function ($pointer) use ($image, $quality): void { imagewebp($image->core()->native(), $pointer, $quality); }, 'image/webp'); } } ================================================ FILE: src/Drivers/Gd/FontProcessor.php ================================================ hasFilename()) { // calculate box size from gd font $box = new Rectangle(0, 0); $chars = mb_strlen($text); if ($chars > 0) { $box->setWidth( $chars * $this->gdCharacterWidth((int) $font->filename()) ); $box->setHeight( $this->gdCharacterHeight((int) $font->filename()) ); } return $box; } // build full path to font file to make sure to pass absolute path to imageftbbox() // because of issues with different GD version behaving differently when passing // relative paths to imageftbbox() $fontPath = realpath($font->filename()); if ($fontPath === false) { throw new FontException('Font file ' . $font->filename() . ' does not exist.'); } // calculate box size from ttf font file with angle 0 $box = imageftbbox( size: $this->nativeFontSize($font), angle: 0, font_filename: $fontPath, string: $text, ); if ($box === false) { throw new FontException('Unable to calculate box size of font ' . $font->filename() . '.'); } // build size from points return new Rectangle( width: intval(abs($box[6] - $box[4])), // difference of upper-left-x and upper-right-x height: intval(abs($box[7] - $box[1])), // difference if upper-left-y and lower-left-y pivot: new Point($box[6], $box[7]), // position of upper-left corner ); } /** * {@inheritdoc} * * @see FontProcessorInterface::nativeFontSize() */ public function nativeFontSize(FontInterface $font): float { return floatval(round($font->size() * .76, 6)); } /** * {@inheritdoc} * * @see FontProcessorInterface::leading() */ public function leading(FontInterface $font): int { return (int) round(parent::leading($font) * .8); } /** * Return width of a single character */ protected function gdCharacterWidth(int $gdfont): int { return $gdfont + 4; } /** * Return height of a single character */ protected function gdCharacterHeight(int $gdfont): int { return match ($gdfont) { 2, 3 => 14, 4, 5 => 16, default => 8, }; } } ================================================ FILE: src/Drivers/Gd/Frame.php ================================================ native = $native; return $this; } /** * {@inheritdoc} * * @see FrameInterface::native() */ public function native(): GdImage { return $this->native; } /** * {@inheritdoc} * * @see FrameInterface::size() */ public function size(): SizeInterface { return new Rectangle(imagesx($this->native), imagesy($this->native)); } /** * {@inheritdoc} * * @see FrameInterface::delay() */ public function delay(): float { return $this->delay; } /** * {@inheritdoc} * * @see FrameInterface::setDelay() */ public function setDelay(float $delay): FrameInterface { $this->delay = $delay; return $this; } /** * {@inheritdoc} * * @see FrameInterface::dispose() */ public function dispose(): int { return $this->dispose; } /** * {@inheritdoc} * * @see FrameInterface::setDispose() * * @throws InputException */ public function setDispose(int $dispose): FrameInterface { if (!in_array($dispose, [0, 1, 2, 3])) { throw new InputException('Value for argument $dispose must be 0, 1, 2 or 3.'); } $this->dispose = $dispose; return $this; } /** * {@inheritdoc} * * @see FrameInterface::setOffset() */ public function setOffset(int $left, int $top): FrameInterface { $this->offset_left = $left; $this->offset_top = $top; return $this; } /** * {@inheritdoc} * * @see FrameInterface::offsetLeft() */ public function offsetLeft(): int { return $this->offset_left; } /** * {@inheritdoc} * * @see FrameInterface::setOffsetLeft() */ public function setOffsetLeft(int $offset): FrameInterface { $this->offset_left = $offset; return $this; } /** * {@inheritdoc} * * @see FrameInterface::offsetTop() */ public function offsetTop(): int { return $this->offset_top; } /** * {@inheritdoc} * * @see FrameInterface::setOffsetTop() */ public function setOffsetTop(int $offset): FrameInterface { $this->offset_top = $offset; return $this; } /** * This workaround helps cloning GdImages which is currently not possible. * * @throws ColorException */ public function __clone(): void { $this->native = Cloner::clone($this->native); } } ================================================ FILE: src/Drivers/Gd/Modifiers/AlignRotationModifier.php ================================================ exif('IFD0.Orientation')) { 2 => $image->flop(), 3 => $image->rotate(180), 4 => $image->rotate(180)->flop(), 5 => $image->rotate(270)->flop(), 6 => $image->rotate(270), 7 => $image->rotate(90)->flop(), 8 => $image->rotate(90), default => $image }; return $this->markAligned($image); } /** * Set exif data of image to top-left orientation, marking the image as * aligned and making sure the rotation correction process is not * performed again. */ private function markAligned(ImageInterface $image): ImageInterface { $exif = $image->exif()->map(function ($item) { if (is_array($item) && array_key_exists('Orientation', $item)) { $item['Orientation'] = 1; return $item; } return $item; }); return $image->setExif($exif); } } ================================================ FILE: src/Drivers/Gd/Modifiers/BlendTransparencyModifier.php ================================================ blendingColor($this->driver()); foreach ($image as $frame) { // create new canvas with blending color as background $modified = Cloner::cloneBlended( $frame->native(), background: $blendingColor ); // set new gd image $frame->setNative($modified); } return $image; } } ================================================ FILE: src/Drivers/Gd/Modifiers/BlurModifier.php ================================================ amount; $i++) { imagefilter($frame->native(), IMG_FILTER_GAUSSIAN_BLUR); } } return $image; } } ================================================ FILE: src/Drivers/Gd/Modifiers/BrightnessModifier.php ================================================ native(), IMG_FILTER_BRIGHTNESS, intval($this->level * 2.55)); } return $image; } } ================================================ FILE: src/Drivers/Gd/Modifiers/ColorizeModifier.php ================================================ red * 2.55); $green = (int) round($this->green * 2.55); $blue = (int) round($this->blue * 2.55); foreach ($image as $frame) { imagefilter($frame->native(), IMG_FILTER_COLORIZE, $red, $green, $blue); } return $image; } } ================================================ FILE: src/Drivers/Gd/Modifiers/ColorspaceModifier.php ================================================ targetColorspace() instanceof RgbColorspace)) { throw new NotSupportedException( 'Only RGB colorspace is supported by GD driver.' ); } return $image; } } ================================================ FILE: src/Drivers/Gd/Modifiers/ContainModifier.php ================================================ getCropSize($image); $resize = $this->getResizeSize($image); $background = $this->driver()->handleInput($this->background); $blendingColor = $this->driver()->handleInput( $this->driver()->config()->blendingColor ); foreach ($image as $frame) { $this->modify($frame, $crop, $resize, $background, $blendingColor); } return $image; } /** * @throws ColorException */ protected function modify( FrameInterface $frame, SizeInterface $crop, SizeInterface $resize, ColorInterface $background, ColorInterface $blendingColor ): void { // create new gd image $modified = Cloner::cloneEmpty($frame->native(), $resize, $background); // make image area transparent to keep transparency // even if background-color is set $transparent = imagecolorallocatealpha( $modified, $blendingColor->channel(Red::class)->value(), $blendingColor->channel(Green::class)->value(), $blendingColor->channel(Blue::class)->value(), 127, ); imagealphablending($modified, false); // do not blend / just overwrite imagecolortransparent($modified, $transparent); imagefilledrectangle( $modified, $crop->pivot()->x(), $crop->pivot()->y(), $crop->pivot()->x() + $crop->width() - 1, $crop->pivot()->y() + $crop->height() - 1, $transparent ); // copy image from original with blending alpha imagealphablending($modified, true); imagecopyresampled( $modified, $frame->native(), $crop->pivot()->x(), $crop->pivot()->y(), 0, 0, $crop->width(), $crop->height(), $frame->size()->width(), $frame->size()->height() ); // set new content as resource $frame->setNative($modified); } } ================================================ FILE: src/Drivers/Gd/Modifiers/ContrastModifier.php ================================================ native(), IMG_FILTER_CONTRAST, ($this->level * -1)); } return $image; } } ================================================ FILE: src/Drivers/Gd/Modifiers/CoverDownModifier.php ================================================ resizeDown($this->width, $this->height); } } ================================================ FILE: src/Drivers/Gd/Modifiers/CoverModifier.php ================================================ getCropSize($image); $resize = $this->getResizeSize($crop); foreach ($image as $frame) { $this->modifyFrame($frame, $crop, $resize); } return $image; } /** * @throws ColorException */ protected function modifyFrame(FrameInterface $frame, SizeInterface $crop, SizeInterface $resize): void { // create new image $modified = Cloner::cloneEmpty($frame->native(), $resize); // copy content from resource imagecopyresampled( $modified, $frame->native(), 0, 0, $crop->pivot()->x(), $crop->pivot()->y(), $resize->width(), $resize->height(), $crop->width(), $crop->height() ); // set new content as resource $frame->setNative($modified); } } ================================================ FILE: src/Drivers/Gd/Modifiers/CropModifier.php ================================================ size(); $crop = $this->crop($image); $background = $this->driver()->handleInput($this->background); foreach ($image as $frame) { $this->cropFrame($frame, $originalSize, $crop, $background); } return $image; } /** * @throws ColorException */ protected function cropFrame( FrameInterface $frame, SizeInterface $originalSize, SizeInterface $resizeTo, ColorInterface $background ): void { // create new image with transparent background $modified = Cloner::cloneEmpty($frame->native(), $resizeTo, $background); // define offset $offset_x = $resizeTo->pivot()->x() + $this->offset_x; $offset_y = $resizeTo->pivot()->y() + $this->offset_y; // define target width & height $targetWidth = min($resizeTo->width(), $originalSize->width()); $targetHeight = min($resizeTo->height(), $originalSize->height()); $targetWidth = $targetWidth < $originalSize->width() ? $targetWidth + $offset_x : $targetWidth; $targetHeight = $targetHeight < $originalSize->height() ? $targetHeight + $offset_y : $targetHeight; // don't alpha blend for copy operation to keep transparent areas of original image imagealphablending($modified, false); // copy content from resource imagecopyresampled( $modified, $frame->native(), $offset_x * -1, $offset_y * -1, 0, 0, $targetWidth, $targetHeight, $targetWidth, $targetHeight ); // set new content as resource $frame->setNative($modified); } } ================================================ FILE: src/Drivers/Gd/Modifiers/DrawBezierModifier.php ================================================ drawable->count() !== 3 && $this->drawable->count() !== 4) { throw new GeometryException('You must specify either 3 or 4 points to create a bezier curve'); } [$polygon, $polygon_border_segments] = $this->calculateBezierPoints(); if ($this->drawable->hasBackgroundColor() || $this->drawable->hasBorder()) { imagealphablending($frame->native(), true); imageantialias($frame->native(), true); } if ($this->drawable->hasBackgroundColor()) { $background_color = $this->driver()->colorProcessor($image->colorspace())->colorToNative( $this->backgroundColor() ); imagesetthickness($frame->native(), 0); imagefilledpolygon( $frame->native(), $polygon, $background_color ); } if ($this->drawable->hasBorder() && $this->drawable->borderSize() > 0) { $border_color = $this->driver()->colorProcessor($image->colorspace())->colorToNative( $this->borderColor() ); if ($this->drawable->borderSize() === 1) { imagesetthickness($frame->native(), $this->drawable->borderSize()); $count = count($polygon); for ($i = 0; $i < $count; $i += 2) { if (array_key_exists($i + 2, $polygon) && array_key_exists($i + 3, $polygon)) { imageline( $frame->native(), $polygon[$i], $polygon[$i + 1], $polygon[$i + 2], $polygon[$i + 3], $border_color ); } } } else { $polygon_border_segments_total = count($polygon_border_segments); for ($i = 0; $i < $polygon_border_segments_total; $i += 1) { imagefilledpolygon( $frame->native(), $polygon_border_segments[$i], $border_color ); } } } } return $image; } /** * Calculate interpolation points for quadratic beziers using the Bernstein polynomial form * * @return array{'x': float, 'y': float} */ private function calculateQuadraticBezierInterpolationPoint(float $t = 0.05): array { $remainder = 1 - $t; $control_point_1_multiplier = $remainder * $remainder; $control_point_2_multiplier = $remainder * $t * 2; $control_point_3_multiplier = $t * $t; $x = ( $this->drawable->first()->x() * $control_point_1_multiplier + $this->drawable->second()->x() * $control_point_2_multiplier + $this->drawable->last()->x() * $control_point_3_multiplier ); $y = ( $this->drawable->first()->y() * $control_point_1_multiplier + $this->drawable->second()->y() * $control_point_2_multiplier + $this->drawable->last()->y() * $control_point_3_multiplier ); return ['x' => $x, 'y' => $y]; } /** * Calculate interpolation points for cubic beziers using the Bernstein polynomial form * * @return array{'x': float, 'y': float} */ private function calculateCubicBezierInterpolationPoint(float $t = 0.05): array { $remainder = 1 - $t; $t_squared = $t * $t; $remainder_squared = $remainder * $remainder; $control_point_1_multiplier = $remainder_squared * $remainder; $control_point_2_multiplier = $remainder_squared * $t * 3; $control_point_3_multiplier = $t_squared * $remainder * 3; $control_point_4_multiplier = $t_squared * $t; $x = ( $this->drawable->first()->x() * $control_point_1_multiplier + $this->drawable->second()->x() * $control_point_2_multiplier + $this->drawable->third()->x() * $control_point_3_multiplier + $this->drawable->last()->x() * $control_point_4_multiplier ); $y = ( $this->drawable->first()->y() * $control_point_1_multiplier + $this->drawable->second()->y() * $control_point_2_multiplier + $this->drawable->third()->y() * $control_point_3_multiplier + $this->drawable->last()->y() * $control_point_4_multiplier ); return ['x' => $x, 'y' => $y]; } /** * Calculate the points needed to draw a quadratic or cubic bezier with optional border/stroke * * @throws GeometryException * @return array{0: array, 1: array} */ private function calculateBezierPoints(): array { if ($this->drawable->count() !== 3 && $this->drawable->count() !== 4) { throw new GeometryException('You must specify either 3 or 4 points to create a bezier curve'); } $polygon = []; $inner_polygon = []; $outer_polygon = []; $polygon_border_segments = []; // define ratio t; equivalent to 5 percent distance along edge $t = 0.05; $polygon[] = $this->drawable->first()->x(); $polygon[] = $this->drawable->first()->y(); for ($i = $t; $i < 1; $i += $t) { if ($this->drawable->count() === 3) { $ip = $this->calculateQuadraticBezierInterpolationPoint($i); } elseif ($this->drawable->count() === 4) { $ip = $this->calculateCubicBezierInterpolationPoint($i); } $polygon[] = (int) $ip['x']; $polygon[] = (int) $ip['y']; } $polygon[] = $this->drawable->last()->x(); $polygon[] = $this->drawable->last()->y(); if ($this->drawable->hasBorder() && $this->drawable->borderSize() > 1) { // create the border/stroke effect by calculating two new curves with offset positions // from the main polygon and then connecting the inner/outer curves to create separate // 4-point polygon segments $polygon_total_points = count($polygon); $offset = ($this->drawable->borderSize() / 2); for ($i = 0; $i < $polygon_total_points; $i += 2) { if (array_key_exists($i + 2, $polygon) && array_key_exists($i + 3, $polygon)) { $dx = $polygon[$i + 2] - $polygon[$i]; $dy = $polygon[$i + 3] - $polygon[$i + 1]; $dxy_sqrt = ($dx * $dx + $dy * $dy) ** 0.5; // inner polygon $scale = $offset / $dxy_sqrt; $ox = -$dy * $scale; $oy = $dx * $scale; $inner_polygon[] = $ox + $polygon[$i]; $inner_polygon[] = $oy + $polygon[$i + 1]; $inner_polygon[] = $ox + $polygon[$i + 2]; $inner_polygon[] = $oy + $polygon[$i + 3]; // outer polygon $scale = -$offset / $dxy_sqrt; $ox = -$dy * $scale; $oy = $dx * $scale; $outer_polygon[] = $ox + $polygon[$i]; $outer_polygon[] = $oy + $polygon[$i + 1]; $outer_polygon[] = $ox + $polygon[$i + 2]; $outer_polygon[] = $oy + $polygon[$i + 3]; } } $inner_polygon_total_points = count($inner_polygon); for ($i = 0; $i < $inner_polygon_total_points; $i += 2) { if (array_key_exists($i + 2, $inner_polygon) && array_key_exists($i + 3, $inner_polygon)) { $polygon_border_segments[] = [ $inner_polygon[$i], $inner_polygon[$i + 1], $outer_polygon[$i], $outer_polygon[$i + 1], $outer_polygon[$i + 2], $outer_polygon[$i + 3], $inner_polygon[$i + 2], $inner_polygon[$i + 3], ]; } } } return [$polygon, $polygon_border_segments]; } } ================================================ FILE: src/Drivers/Gd/Modifiers/DrawEllipseModifier.php ================================================ drawable->hasBorder()) { imagealphablending($frame->native(), true); // slightly smaller ellipse to keep 1px bordered edges clean if ($this->drawable->hasBackgroundColor()) { imagefilledellipse( $frame->native(), $this->drawable()->position()->x(), $this->drawable->position()->y(), $this->drawable->width() - 1, $this->drawable->height() - 1, $this->driver()->colorProcessor($image->colorspace())->colorToNative( $this->backgroundColor() ) ); } // gd's imageellipse ignores imagesetthickness // so i use imagearc with 360 degrees instead. imagesetthickness( $frame->native(), $this->drawable->borderSize(), ); imagearc( $frame->native(), $this->drawable()->position()->x(), $this->drawable()->position()->y(), $this->drawable->width(), $this->drawable->height(), 0, 360, $this->driver()->colorProcessor($image->colorspace())->colorToNative( $this->borderColor() ) ); } else { imagealphablending($frame->native(), true); imagesetthickness($frame->native(), 0); imagefilledellipse( $frame->native(), $this->drawable()->position()->x(), $this->drawable()->position()->y(), $this->drawable->width(), $this->drawable->height(), $this->driver()->colorProcessor($image->colorspace())->colorToNative( $this->backgroundColor() ) ); } } return $image; } } ================================================ FILE: src/Drivers/Gd/Modifiers/DrawLineModifier.php ================================================ driver()->colorProcessor($image->colorspace())->colorToNative( $this->backgroundColor() ); foreach ($image as $frame) { imagealphablending($frame->native(), true); imageantialias($frame->native(), true); imagesetthickness($frame->native(), $this->drawable->width()); imageline( $frame->native(), $this->drawable->start()->x(), $this->drawable->start()->y(), $this->drawable->end()->x(), $this->drawable->end()->y(), $color ); } return $image; } } ================================================ FILE: src/Drivers/Gd/Modifiers/DrawPixelModifier.php ================================================ driver()->colorProcessor($image->colorspace())->colorToNative( $this->driver()->handleInput($this->color) ); foreach ($image as $frame) { imagealphablending($frame->native(), true); imagesetpixel( $frame->native(), $this->position->x(), $this->position->y(), $color ); } return $image; } } ================================================ FILE: src/Drivers/Gd/Modifiers/DrawPolygonModifier.php ================================================ drawable->hasBackgroundColor()) { imagealphablending($frame->native(), true); imagesetthickness($frame->native(), 0); imagefilledpolygon( $frame->native(), $this->drawable->toArray(), $this->driver()->colorProcessor($image->colorspace())->colorToNative( $this->backgroundColor() ) ); } if ($this->drawable->hasBorder()) { imagealphablending($frame->native(), true); imagesetthickness($frame->native(), $this->drawable->borderSize()); imagepolygon( $frame->native(), $this->drawable->toArray(), $this->driver()->colorProcessor($image->colorspace())->colorToNative( $this->borderColor() ) ); } } return $image; } } ================================================ FILE: src/Drivers/Gd/Modifiers/DrawRectangleModifier.php ================================================ drawable->position(); foreach ($image as $frame) { // draw background if ($this->drawable->hasBackgroundColor()) { imagealphablending($frame->native(), true); imagesetthickness($frame->native(), 0); imagefilledrectangle( $frame->native(), $position->x(), $position->y(), $position->x() + $this->drawable->width(), $position->y() + $this->drawable->height(), $this->driver()->colorProcessor($image->colorspace())->colorToNative( $this->backgroundColor() ) ); } // draw border if ($this->drawable->hasBorder()) { imagealphablending($frame->native(), true); imagesetthickness($frame->native(), $this->drawable->borderSize()); imagerectangle( $frame->native(), $position->x(), $position->y(), $position->x() + $this->drawable->width(), $position->y() + $this->drawable->height(), $this->driver()->colorProcessor($image->colorspace())->colorToNative( $this->borderColor() ) ); } } return $image; } } ================================================ FILE: src/Drivers/Gd/Modifiers/FillModifier.php ================================================ color($image); foreach ($image as $frame) { if ($this->hasPosition()) { $this->floodFillWithColor($frame, $color); } else { $this->fillAllWithColor($frame, $color); } } return $image; } /** * @throws RuntimeException */ private function color(ImageInterface $image): int { return $this->driver()->colorProcessor($image->colorspace())->colorToNative( $this->driver()->handleInput($this->color) ); } private function floodFillWithColor(FrameInterface $frame, int $color): void { imagefill( $frame->native(), $this->position->x(), $this->position->y(), $color ); } private function fillAllWithColor(FrameInterface $frame, int $color): void { imagealphablending($frame->native(), true); imagefilledrectangle( $frame->native(), 0, 0, $frame->size()->width() - 1, $frame->size()->height() - 1, $color ); } } ================================================ FILE: src/Drivers/Gd/Modifiers/FlipModifier.php ================================================ native(), IMG_FLIP_VERTICAL); } return $image; } } ================================================ FILE: src/Drivers/Gd/Modifiers/FlopModifier.php ================================================ native(), IMG_FLIP_HORIZONTAL); } return $image; } } ================================================ FILE: src/Drivers/Gd/Modifiers/GammaModifier.php ================================================ native(), 1, $this->gamma); } return $image; } } ================================================ FILE: src/Drivers/Gd/Modifiers/GreyscaleModifier.php ================================================ native(), IMG_FILTER_GRAYSCALE); } return $image; } } ================================================ FILE: src/Drivers/Gd/Modifiers/InvertModifier.php ================================================ native(), IMG_FILTER_NEGATE); } return $image; } } ================================================ FILE: src/Drivers/Gd/Modifiers/PadModifier.php ================================================ size() ->containMax( $this->width, $this->height ) ->alignPivotTo( $this->getResizeSize($image), $this->position ); } } ================================================ FILE: src/Drivers/Gd/Modifiers/PixelateModifier.php ================================================ native(), IMG_FILTER_PIXELATE, $this->size, true); } return $image; } } ================================================ FILE: src/Drivers/Gd/Modifiers/PlaceModifier.php ================================================ driver()->handleInput($this->element); $position = $this->getPosition($image, $watermark); foreach ($image as $frame) { imagealphablending($frame->native(), true); if ($this->opacity === 100) { $this->placeOpaque($frame, $watermark, $position); } else { $this->placeTransparent($frame, $watermark, $position); } } return $image; } /** * Insert watermark with 100% opacity * * @throws RuntimeException */ private function placeOpaque(FrameInterface $frame, ImageInterface $watermark, PointInterface $position): void { imagecopy( $frame->native(), $watermark->core()->native(), $position->x(), $position->y(), 0, 0, $watermark->width(), $watermark->height() ); } /** * Insert watermark transparent with current opacity * * Unfortunately, the original PHP function imagecopymerge does not work reliably. * For example, any transparency of the image to be inserted is not applied correctly. * For this reason, a new GDImage is created into which the original image is inserted * in the first step and the watermark is inserted with 100% opacity in the second * step. This combination is then transferred to the original image again with the * respective opacity. * * Please note: Unfortunately, there is still an edge case, when a transparent image * is placed on a transparent background, the "double" transparent areas appear opaque! * * @throws RuntimeException */ private function placeTransparent(FrameInterface $frame, ImageInterface $watermark, PointInterface $position): void { $cut = imagecreatetruecolor($watermark->width(), $watermark->height()); imagecopy( $cut, $frame->native(), 0, 0, $position->x(), $position->y(), imagesx($cut), imagesy($cut) ); imagecopy( $cut, $watermark->core()->native(), 0, 0, 0, 0, imagesx($cut), imagesy($cut) ); imagecopymerge( $frame->native(), $cut, $position->x(), $position->y(), 0, 0, $watermark->width(), $watermark->height(), $this->opacity ); } } ================================================ FILE: src/Drivers/Gd/Modifiers/ProfileModifier.php ================================================ limit <= 0) { throw new InputException('Quantization limit must be greater than 0.'); } // no color reduction if the limit is higher than the colors in the img $colorCount = imagecolorstotal($image->core()->native()); if ($colorCount > 0 && $this->limit > $colorCount) { return $image; } $width = $image->width(); $height = $image->height(); $background = $this->driver()->colorProcessor($image->colorspace())->colorToNative( $this->driver()->handleInput($this->background) ); $blendingColor = $this->driver()->handleInput( $this->driver()->config()->blendingColor ); foreach ($image as $frame) { // create new image for color quantization $reduced = Cloner::cloneEmpty($frame->native(), background: $blendingColor); // fill with background imagefill($reduced, 0, 0, $background); // set transparency imagecolortransparent($reduced, $background); // copy original image (colors are limited automatically in the copy process) imagecopy($reduced, $frame->native(), 0, 0, 0, 0, $width, $height); // gd library does not support color quantization directly therefore the // colors are decrease by transforming the image to a palette version imagetruecolortopalette($reduced, true, $this->limit); $frame->setNative($reduced); } return $image; } } ================================================ FILE: src/Drivers/Gd/Modifiers/RemoveAnimationModifier.php ================================================ core()->setNative( $this->selectedFrame($image)->native() ); return $image; } } ================================================ FILE: src/Drivers/Gd/Modifiers/ResizeCanvasModifier.php ================================================ cropSize($image); $image->modify(new CropModifier( $cropSize->width(), $cropSize->height(), $cropSize->pivot()->x(), $cropSize->pivot()->y(), $this->background, )); return $image; } } ================================================ FILE: src/Drivers/Gd/Modifiers/ResizeCanvasRelativeModifier.php ================================================ size()->resizeDown($this->width, $this->height); } } ================================================ FILE: src/Drivers/Gd/Modifiers/ResizeModifier.php ================================================ getAdjustedSize($image); foreach ($image as $frame) { $this->resizeFrame($frame, $resizeTo); } return $image; } /** * @throws ColorException */ private function resizeFrame(FrameInterface $frame, SizeInterface $resizeTo): void { // create empty canvas in target size $modified = Cloner::cloneEmpty($frame->native(), $resizeTo); // copy content from resource imagecopyresampled( $modified, $frame->native(), $resizeTo->pivot()->x(), $resizeTo->pivot()->y(), 0, 0, $resizeTo->width(), $resizeTo->height(), $frame->size()->width(), $frame->size()->height() ); // set new content as resource $frame->setNative($modified); } /** * Return the size the modifier will resize to * * @throws RuntimeException * @throws GeometryException */ protected function getAdjustedSize(ImageInterface $image): SizeInterface { return $image->size()->resize($this->width, $this->height); } } ================================================ FILE: src/Drivers/Gd/Modifiers/ResolutionModifier.php ================================================ x)); $y = intval(round($this->y)); foreach ($image as $frame) { imageresolution($frame->native(), $x, $y); } return $image; } } ================================================ FILE: src/Drivers/Gd/Modifiers/RotateModifier.php ================================================ driver()->handleInput($this->background); foreach ($image as $frame) { $this->modifyFrame($frame, $background); } return $image; } /** * Apply rotation modification on given frame, given background * color is used for newly create image areas * * @throws ColorException */ protected function modifyFrame(FrameInterface $frame, ColorInterface $background): void { // get transparent color from frame core $transparent = match ($transparent = imagecolortransparent($frame->native())) { -1 => imagecolorallocatealpha( $frame->native(), $background->channel(Red::class)->value(), $background->channel(Green::class)->value(), $background->channel(Blue::class)->value(), 127 ), default => $transparent, }; // rotate original image against transparent background $rotated = imagerotate( $frame->native(), $this->rotationAngle(), $transparent ); // create size from original after rotation $container = (new Rectangle( imagesx($rotated), imagesy($rotated), ))->movePivot('center'); // create size from original and rotate points $cutout = (new Rectangle( imagesx($frame->native()), imagesy($frame->native()), $container->pivot() ))->align('center') ->valign('center') ->rotate($this->rotationAngle() * -1); // create new gd image $modified = Cloner::cloneEmpty($frame->native(), $container, $background); // draw the cutout on new gd image to have a transparent // background where the rotated image will be placed imagealphablending($modified, false); imagefilledpolygon( $modified, $cutout->toArray(), imagecolortransparent($modified) ); // place rotated image on new gd image imagealphablending($modified, true); imagecopy( $modified, $rotated, 0, 0, 0, 0, imagesx($rotated), imagesy($rotated) ); $frame->setNative($modified); } } ================================================ FILE: src/Drivers/Gd/Modifiers/ScaleDownModifier.php ================================================ size()->scaleDown($this->width, $this->height); } } ================================================ FILE: src/Drivers/Gd/Modifiers/ScaleModifier.php ================================================ size()->scale($this->width, $this->height); } } ================================================ FILE: src/Drivers/Gd/Modifiers/SharpenModifier.php ================================================ matrix(); foreach ($image as $frame) { imageconvolution($frame->native(), $matrix, 1, 0); } return $image; } /** * Create matrix to be used by imageconvolution() * * @return array> */ private function matrix(): array { $min = $this->amount >= 10 ? $this->amount * -0.01 : 0; $max = $this->amount * -0.025; $abs = ((4 * $min + 4 * $max) * -1) + 1; return [ [$min, $max, $min], [$max, $abs, $max], [$min, $max, $min] ]; } } ================================================ FILE: src/Drivers/Gd/Modifiers/SliceAnimationModifier.php ================================================ offset >= $image->count()) { throw new AnimationException('Offset is not in the range of frames.'); } $image->core()->slice($this->offset, $this->length); return $image; } } ================================================ FILE: src/Drivers/Gd/Modifiers/TextModifier.php ================================================ driver()->fontProcessor(); $lines = $fontProcessor->textBlock($this->text, $this->font, $this->position); // decode text colors $textColor = $this->gdTextColor($image); $strokeColor = $this->gdStrokeColor($image); // build full path to font file to make sure to pass absolute path to imageftbbox() // because of issues with different GD version behaving differently when passing // relative paths to imagettftext() $fontPath = $this->font->hasFilename() ? realpath($this->font->filename()) : false; if ($this->font->hasFilename() && $fontPath === false) { throw new FontException('Font file ' . $this->font->filename() . ' does not exist.'); } foreach ($image as $frame) { imagealphablending($frame->native(), true); if ($this->font->hasFilename()) { foreach ($lines as $line) { foreach ($this->strokeOffsets($this->font) as $offset) { imagettftext( image: $frame->native(), size: $fontProcessor->nativeFontSize($this->font), angle: $this->font->angle() * -1, x: $line->position()->x() + $offset->x(), y: $line->position()->y() + $offset->y(), color: $strokeColor, font_filename: $fontPath, text: (string) $line ); } imagettftext( image: $frame->native(), size: $fontProcessor->nativeFontSize($this->font), angle: $this->font->angle() * -1, x: $line->position()->x(), y: $line->position()->y(), color: $textColor, font_filename: $fontPath, text: (string) $line ); } } else { foreach ($lines as $line) { foreach ($this->strokeOffsets($this->font) as $offset) { imagestring( $frame->native(), $this->gdFont(), $line->position()->x() + $offset->x(), $line->position()->y() + $offset->y(), (string) $line, $strokeColor ); } imagestring( $frame->native(), $this->gdFont(), $line->position()->x(), $line->position()->y(), (string) $line, $textColor ); } } } return $image; } /** * Decode text color in GD compatible format * * @throws RuntimeException * @throws ColorException */ protected function gdTextColor(ImageInterface $image): int { return $this ->driver() ->colorProcessor($image->colorspace()) ->colorToNative(parent::textColor()); } /** * Decode color for stroke (outline) effect in GD compatible format * * @throws RuntimeException * @throws ColorException */ protected function gdStrokeColor(ImageInterface $image): int { if (!$this->font->hasStrokeEffect()) { return 0; } $color = parent::strokeColor(); if ($color->isTransparent()) { throw new ColorException( 'The stroke color must be fully opaque.' ); } return $this ->driver() ->colorProcessor($image->colorspace()) ->colorToNative($color); } /** * Return GD's internal font size (if no ttf file is set) */ private function gdFont(): int { if (is_numeric($this->font->filename())) { return intval($this->font->filename()); } return 1; } } ================================================ FILE: src/Drivers/Gd/Modifiers/TrimModifier.php ================================================ isAnimated()) { throw new NotSupportedException('Trim modifier cannot be applied to animated images.'); } // apply tolerance with a min. value of .5 because the default tolerance of '0' should // already trim away similar colors which is not the case with imagecropauto. $trimmed = imagecropauto( $image->core()->native(), IMG_CROP_THRESHOLD, max([.5, $this->tolerance / 10]), $this->trimColor($image) ); // if the tolerance is very high, it is possible that no image is left. // imagick returns a 1x1 pixel image in this case. this does the same. if ($trimmed === false) { $trimmed = $this->driver()->createImage(1, 1)->core()->native(); } $image->core()->setNative($trimmed); return $image; } /** * Create an average color from the colors of the four corner points of the given image * * @throws RuntimeException * @throws AnimationException */ private function trimColor(ImageInterface $image): int { // trim color base $red = 0; $green = 0; $blue = 0; // corner coordinates $size = $image->size(); $cornerPoints = [ new Point(0, 0), new Point($size->width() - 1, 0), new Point(0, $size->height() - 1), new Point($size->width() - 1, $size->height() - 1), ]; // create an average color to be used in trim operation foreach ($cornerPoints as $pos) { $cornerColor = imagecolorat($image->core()->native(), $pos->x(), $pos->y()); $rgb = imagecolorsforindex($image->core()->native(), $cornerColor); $red += round(round(($rgb['red'] / 51)) * 51); $green += round(round(($rgb['green'] / 51)) * 51); $blue += round(round(($rgb['blue'] / 51)) * 51); } $red = (int) round($red / 4); $green = (int) round($green / 4); $blue = (int) round($blue / 4); return imagecolorallocate($image->core()->native(), $red, $green, $blue); } } ================================================ FILE: src/Drivers/Imagick/Analyzers/ColorspaceAnalyzer.php ================================================ core()->native()->getImageColorspace()) { Imagick::COLORSPACE_CMYK => new CmykColorspace(), default => new RgbColorspace(), }; } } ================================================ FILE: src/Drivers/Imagick/Analyzers/HeightAnalyzer.php ================================================ core()->native()->getImageHeight(); } } ================================================ FILE: src/Drivers/Imagick/Analyzers/PixelColorAnalyzer.php ================================================ colorAt( $image->colorspace(), $image->core()->frame($this->frame_key)->native() ); } /** * @throws ColorException */ protected function colorAt(ColorspaceInterface $colorspace, Imagick $imagick): ColorInterface { return $this->driver() ->colorProcessor($colorspace) ->nativeToColor( $imagick->getImagePixelColor($this->x, $this->y) ); } } ================================================ FILE: src/Drivers/Imagick/Analyzers/PixelColorsAnalyzer.php ================================================ colorspace(); foreach ($image as $frame) { $colors->push( parent::colorAt($colorspace, $frame->native()) ); } return $colors; } } ================================================ FILE: src/Drivers/Imagick/Analyzers/ProfileAnalyzer.php ================================================ core()->native()->getImageProfiles('icc'); if (!array_key_exists('icc', $profiles)) { throw new ColorException('No ICC profile found in image.'); } return new Profile($profiles['icc']); } } ================================================ FILE: src/Drivers/Imagick/Analyzers/ResolutionAnalyzer.php ================================================ core()->native(); $imageResolution = $imagick->getImageResolution(); return new Resolution( $imageResolution['x'], $imageResolution['y'], $imagick->getImageUnits(), ); } } ================================================ FILE: src/Drivers/Imagick/Analyzers/WidthAnalyzer.php ================================================ core()->native()->getImageWidth(); } } ================================================ FILE: src/Drivers/Imagick/ColorProcessor.php ================================================ convertTo($this->colorspace) ); } public function nativeToColor(mixed $native): ColorInterface { return match ($this->colorspace::class) { CmykColorspace::class => $this->colorspace->colorFromNormalized([ $native->getColorValue(Imagick::COLOR_CYAN), $native->getColorValue(Imagick::COLOR_MAGENTA), $native->getColorValue(Imagick::COLOR_YELLOW), $native->getColorValue(Imagick::COLOR_BLACK), ]), default => $this->colorspace->colorFromNormalized([ $native->getColorValue(Imagick::COLOR_RED), $native->getColorValue(Imagick::COLOR_GREEN), $native->getColorValue(Imagick::COLOR_BLUE), $native->getColorValue(Imagick::COLOR_ALPHA), ]), }; } } ================================================ FILE: src/Drivers/Imagick/Core.php ================================================ */ class Core implements CoreInterface, Iterator { protected int $iteratorIndex = 0; /** * Create new core instance * * @return void */ public function __construct(protected Imagick $imagick) { // } /** * {@inheritdoc} * * @see CollectionInterface::has() */ public function has(int|string $key): bool { try { $result = $this->imagick->setIteratorIndex($key); } catch (ImagickException) { return false; } return $result; } /** * {@inheritdoc} * * @see CollectionInterface::push() */ public function push(mixed $item): CollectionInterface { return $this->add($item); } /** * {@inheritdoc} * * @see CollectionInterface::get() */ public function get(int|string $key, mixed $default = null): mixed { try { $this->imagick->setIteratorIndex($key); } catch (ImagickException) { return $default; } return new Frame($this->imagick->current()); } /** * {@inheritdoc} * * @see CollectionInterface::getAtPosition() */ public function getAtPosition(int $key = 0, mixed $default = null): mixed { return $this->get($key, $default); } /** * {@inheritdoc} * * @see CollectionInterface::empty() */ public function empty(): CollectionInterface { $this->imagick->clear(); return $this; } /** * {@inheritdoc} * * @see CollectionInterface::slice() */ public function slice(int $offset, ?int $length = null): CollectionInterface { $allowed_indexes = []; $length = is_null($length) ? $this->count() : $length; for ($i = $offset; $i < $offset + $length; $i++) { $allowed_indexes[] = $i; } $sliced = new Imagick(); foreach ($this->imagick as $key => $native) { if (in_array($key, $allowed_indexes)) { $sliced->addImage($native->getImage()); } } $sliced = $sliced->coalesceImages(); $sliced->setImageIterations($this->imagick->getImageIterations()); $this->imagick = $sliced; return $this; } /** * {@inheritdoc} * * @see CoreInterface::add() */ public function add(FrameInterface $frame): CoreInterface { $imagick = $frame->native(); $imagick->setImageDelay( (int) round($frame->delay() * 100) ); $imagick->setImageDispose($frame->dispose()); $size = $frame->size(); $imagick->setImagePage( $size->width(), $size->height(), $frame->offsetLeft(), $frame->offsetTop() ); $this->imagick->addImage($imagick); return $this; } /** * {@inheritdoc} * * @see CoreInterface::count() */ public function count(): int { return $this->imagick->getNumberImages(); } /** * {@inheritdoc} * * @see Iterator::rewind() */ public function current(): mixed { $this->imagick->setIteratorIndex($this->iteratorIndex); return new Frame($this->imagick->current()); } /** * {@inheritdoc} * * @see Iterator::rewind() */ public function next(): void { $this->iteratorIndex += 1; } /** * {@inheritdoc} * * @see Iterator::rewind() */ public function key(): mixed { return $this->iteratorIndex; } /** * {@inheritdoc} * * @see Iterator::rewind() */ public function valid(): bool { try { $result = $this->imagick->setIteratorIndex($this->iteratorIndex); } catch (ImagickException) { return false; } return $result; } /** * {@inheritdoc} * * @see Iterator::rewind() */ public function rewind(): void { $this->iteratorIndex = 0; } /** * {@inheritdoc} * * @see CoreInterface::native() */ public function native(): mixed { return $this->imagick; } /** * {@inheritdoc} * * @see CoreInterface::setNative() */ public function setNative(mixed $native): CoreInterface { $this->imagick = $native; return $this; } /** * {@inheritdoc} * * @see CoreInterface::frame() */ public function frame(int $position): FrameInterface { foreach ($this->imagick as $core) { if ($core->getIteratorIndex() === $position) { return new Frame($core); } } throw new AnimationException('Frame #' . $position . ' could not be found in the image.'); } /** * {@inheritdoc} * * @see CoreInterface::loops() */ public function loops(): int { return $this->imagick->getImageIterations(); } /** * {@inheritdoc} * * @see CoreInterface::setLoops() */ public function setLoops(int $loops): CoreInterface { $this->imagick = $this->imagick->coalesceImages(); $this->imagick->setImageIterations($loops); return $this; } /** * {@inheritdoc} * * @see CollectionInterface::first() */ public function first(): FrameInterface { return $this->frame(0); } /** * {@inheritdoc} * * @see CollectableInterface::last() */ public function last(): FrameInterface { return $this->frame($this->count() - 1); } /** * {@inheritdoc} * * @see CollectionInterface::toArray() */ public function toArray(): array { $frames = []; foreach ($this as $frame) { $frames[] = $frame; } return $frames; } /** * Clone instance */ public function __clone(): void { $this->imagick = clone $this->imagick; } } ================================================ FILE: src/Drivers/Imagick/Decoders/Base64ImageDecoder.php ================================================ isValidBase64($input)) { throw new DecoderException('Unable to decode input'); } return parent::decode(base64_decode((string) $input)); } } ================================================ FILE: src/Drivers/Imagick/Decoders/BinaryImageDecoder.php ================================================ readImageBlob($input); } catch (ImagickException) { throw new DecoderException('Unable to decode input'); } // decode image $image = parent::decode($imagick); // get media type enum from string media type $format = Format::tryCreate($image->origin()->mediaType()); // extract exif data for appropriate formats if (in_array($format, [Format::JPEG, Format::TIFF])) { $image->setExif($this->extractExifData($input)); } return $image; } } ================================================ FILE: src/Drivers/Imagick/Decoders/DataUriImageDecoder.php ================================================ parseDataUri($input); if (!$uri->isValid()) { throw new DecoderException('Unable to decode input'); } if ($uri->isBase64Encoded()) { return parent::decode(base64_decode($uri->data())); } return parent::decode(urldecode($uri->data())); } } ================================================ FILE: src/Drivers/Imagick/Decoders/EncodedImageObjectDecoder.php ================================================ toString()); } } ================================================ FILE: src/Drivers/Imagick/Decoders/FilePathImageDecoder.php ================================================ isFile($input)) { throw new DecoderException('Unable to decode input'); } try { $imagick = new Imagick(); $imagick->readImage($input); } catch (ImagickException) { throw new DecoderException('Unable to decode input'); } // decode image $image = parent::decode($imagick); // set file path on origin $image->origin()->setFilePath($input); // extract exif data for the appropriate formats if (in_array($imagick->getImageFormat(), ['JPEG', 'TIFF', 'TIF'])) { $image->setExif($this->extractExifData($input)); } return $image; } } ================================================ FILE: src/Drivers/Imagick/Decoders/FilePointerImageDecoder.php ================================================ getImageFormat() !== 'JPEG') { $input = $input->coalesceImages(); } // turn images with colorspace 'GRAY' into 'SRGB' to avoid working on // greyscale colorspace images as this results images loosing color // information when placed into this image. if ($input->getImageColorspace() == Imagick::COLORSPACE_GRAY) { $input->setImageColorspace(Imagick::COLORSPACE_SRGB); } // create image object $image = new Image( $this->driver(), new Core($input) ); // discard animation depending on config if (!$this->driver()->config()->decodeAnimation) { $image->modify(new RemoveAnimationModifier()); } // adjust image rotatation if ($this->driver()->config()->autoOrientation) { $image->modify(new AlignRotationModifier()); } // set media type on origin $image->origin()->setMediaType($input->getImageMimeType()); return $image; } } ================================================ FILE: src/Drivers/Imagick/Decoders/SplFileInfoImageDecoder.php ================================================ getRealPath()); } } ================================================ FILE: src/Drivers/Imagick/Driver.php ================================================ newImage($width, $height, $background, 'png'); $imagick->setType(Imagick::IMGTYPE_UNDEFINED); $imagick->setImageType(Imagick::IMGTYPE_UNDEFINED); $imagick->setColorspace(Imagick::COLORSPACE_SRGB); $imagick->setImageResolution(96, 96); $imagick->setImageBackgroundColor($background); return new Image($this, new Core($imagick)); } /** * {@inheritdoc} * * @see DriverInterface::createAnimation() * * @throws RuntimeException */ public function createAnimation(callable $init): ImageInterface { $imagick = new Imagick(); $imagick->setFormat('gif'); $animation = new class ($this, $imagick) { public function __construct( protected DriverInterface $driver, public Imagick $imagick ) { // } /** * @throws RuntimeException */ public function add(mixed $source, float $delay = 1): self { $native = $this->driver->handleInput($source)->core()->native(); $native->setImageDelay(intval(round($delay * 100))); $this->imagick->addImage($native); return $this; } /** * @throws RuntimeException */ public function __invoke(): ImageInterface { return new Image( $this->driver, new Core($this->imagick) ); } }; $init($animation); return call_user_func($animation); } /** * {@inheritdoc} * * @see DriverInterface::colorProcessor() */ public function colorProcessor(ColorspaceInterface $colorspace): ColorProcessorInterface { return new ColorProcessor($colorspace); } /** * {@inheritdoc} * * @see DriverInterface::fontProcessor() */ public function fontProcessor(): FontProcessorInterface { return new FontProcessor(); } /** * {@inheritdoc} * * @see DriverInterface::supports() */ public function supports(string|Format|FileExtension|MediaType $identifier): bool { try { $format = Format::create($identifier); } catch (NotSupportedException) { return false; } return count(Imagick::queryFormats($format->name)) >= 1; } /** * Return version of ImageMagick library * * @throws DriverException */ public static function version(): string { $pattern = '/^ImageMagick (?P(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)' . '(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?' . '(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?)/'; if (preg_match($pattern, Imagick::getVersion()['versionString'], $matches) !== 1) { throw new DriverException('Unable to read ImageMagick version number.'); } return $matches['version']; } } ================================================ FILE: src/Drivers/Imagick/Encoders/AvifEncoder.php ================================================ strip || (is_null($this->strip) && $this->driver()->config()->strip)) { $image->modify(new StripMetaModifier()); } $imagick = $image->core()->native(); $imagick->setFormat($format); $imagick->setImageFormat($format); $imagick->setCompression($compression); $imagick->setImageCompression($compression); $imagick->setCompressionQuality($this->quality); $imagick->setImageCompressionQuality($this->quality); return new EncodedImage($imagick->getImagesBlob(), 'image/avif'); } } ================================================ FILE: src/Drivers/Imagick/Encoders/BmpEncoder.php ================================================ core()->native(); $imagick->setFormat($format); $imagick->setImageFormat($format); $imagick->setCompression($compression); $imagick->setImageCompression($compression); return new EncodedImage($imagick->getImagesBlob(), 'image/bmp'); } } ================================================ FILE: src/Drivers/Imagick/Encoders/GifEncoder.php ================================================ core()->native(); $imagick->setFormat($format); $imagick->setImageFormat($format); $imagick->setCompression($compression); $imagick->setImageCompression($compression); if ($this->interlaced) { $imagick->setInterlaceScheme(Imagick::INTERLACE_LINE); } return new EncodedImage($imagick->getImagesBlob(), 'image/gif'); } } ================================================ FILE: src/Drivers/Imagick/Encoders/HeicEncoder.php ================================================ strip || (is_null($this->strip) && $this->driver()->config()->strip)) { $image->modify(new StripMetaModifier()); } $imagick = $image->core()->native(); $imagick->setFormat($format); $imagick->setImageFormat($format); $imagick->setCompressionQuality($this->quality); $imagick->setImageCompressionQuality($this->quality); return new EncodedImage($imagick->getImagesBlob(), 'image/heic'); } } ================================================ FILE: src/Drivers/Imagick/Encoders/Jpeg2000Encoder.php ================================================ strip || (is_null($this->strip) && $this->driver()->config()->strip)) { $image->modify(new StripMetaModifier()); } $imagick = $image->core()->native(); $imagick->setImageBackgroundColor('white'); $imagick->setBackgroundColor('white'); $imagick->setFormat($format); $imagick->setImageFormat($format); $imagick->setCompression($compression); $imagick->setImageCompression($compression); $imagick->setCompressionQuality($this->quality); $imagick->setImageCompressionQuality($this->quality); return new EncodedImage($imagick->getImagesBlob(), 'image/jp2'); } } ================================================ FILE: src/Drivers/Imagick/Encoders/JpegEncoder.php ================================================ driver()->handleInput( $this->driver()->config()->blendingColor ); // resolve blending color because jpeg has no transparency $background = $this->driver() ->colorProcessor($image->colorspace()) ->colorToNative($blendingColor); // set alpha value to 1 because Imagick renders // possible full transparent colors as black $background->setColorValue(Imagick::COLOR_ALPHA, 1); // strip meta data if ($this->strip || (is_null($this->strip) && $this->driver()->config()->strip)) { $image->modify(new StripMetaModifier()); } /** @var Imagick $imagick */ $imagick = $image->core()->native(); $imagick->setImageBackgroundColor($background); $imagick->setBackgroundColor($background); $imagick->setFormat($format); $imagick->setImageFormat($format); $imagick->setCompression($compression); $imagick->setImageCompression($compression); $imagick->setCompressionQuality($this->quality); $imagick->setImageCompressionQuality($this->quality); $imagick->setImageAlphaChannel(Imagick::ALPHACHANNEL_REMOVE); if ($this->progressive) { $imagick->setInterlaceScheme(Imagick::INTERLACE_PLANE); } return new EncodedImage($imagick->getImagesBlob(), 'image/jpeg'); } } ================================================ FILE: src/Drivers/Imagick/Encoders/PngEncoder.php ================================================ indexed) { // reduce colors $output = clone $image; $output->reduceColors(256); $output = $output->core()->native(); $output->setFormat('PNG'); $output->setImageFormat('PNG'); } else { $output = clone $image->core()->native(); $output->setFormat('PNG32'); $output->setImageFormat('PNG32'); } $output->setCompression(Imagick::COMPRESSION_ZIP); $output->setImageCompression(Imagick::COMPRESSION_ZIP); if ($this->interlaced) { $output->setInterlaceScheme(Imagick::INTERLACE_LINE); } return new EncodedImage($output->getImagesBlob(), 'image/png'); } } ================================================ FILE: src/Drivers/Imagick/Encoders/TiffEncoder.php ================================================ strip || (is_null($this->strip) && $this->driver()->config()->strip)) { $image->modify(new StripMetaModifier()); } $imagick = $image->core()->native(); $imagick->setFormat($format); $imagick->setImageFormat($format); $imagick->setCompression($imagick->getImageCompression()); $imagick->setImageCompression($imagick->getImageCompression()); $imagick->setCompressionQuality($this->quality); $imagick->setImageCompressionQuality($this->quality); return new EncodedImage($imagick->getImagesBlob(), 'image/tiff'); } } ================================================ FILE: src/Drivers/Imagick/Encoders/WebpEncoder.php ================================================ strip || (is_null($this->strip) && $this->driver()->config()->strip)) { $image->modify(new StripMetaModifier()); } $imagick = $image->core()->native(); $imagick->setImageBackgroundColor(new ImagickPixel('transparent')); if (!$image->isAnimated()) { $imagick = $imagick->mergeImageLayers(Imagick::LAYERMETHOD_MERGE); } $imagick->setFormat($format); $imagick->setImageFormat($format); $imagick->setCompression($compression); $imagick->setImageCompression($compression); $imagick->setImageCompressionQuality($this->quality); if ($this->quality === 100) { $imagick->setOption('webp:lossless', 'true'); } return new EncodedImage($imagick->getImagesBlob(), 'image/webp'); } } ================================================ FILE: src/Drivers/Imagick/FontProcessor.php ================================================ toImagickDraw($font); $dimensions = (new Imagick())->queryFontMetrics($draw, $text); return new Rectangle( intval(round($dimensions['textWidth'])), intval(round($dimensions['ascender'] + $dimensions['descender'])), ); } /** * Imagick::annotateImage() needs an ImagickDraw object - this method takes * the font object as the base and adds an optional passed color to the new * ImagickDraw object. * * @throws FontException * @throws ImagickDrawException * @throws ImagickException */ public function toImagickDraw(FontInterface $font, ?ImagickPixel $color = null): ImagickDraw { if (!$font->hasFilename()) { throw new FontException('No font file specified.'); } $draw = new ImagickDraw(); $draw->setStrokeAntialias(true); $draw->setTextAntialias(true); $draw->setFont($font->filename()); $draw->setFontSize($this->nativeFontSize($font)); $draw->setTextAlignment(Imagick::ALIGN_LEFT); if ($color instanceof ImagickPixel) { $draw->setFillColor($color); } return $draw; } } ================================================ FILE: src/Drivers/Imagick/Frame.php ================================================ native->setImageBackgroundColor($background); $this->native->setBackgroundColor($background); } /** * {@inheritdoc} * * @see DriverInterface::toImage() */ public function toImage(DriverInterface $driver): ImageInterface { return new Image($driver, new Core($this->native())); } /** * {@inheritdoc} * * @see DriverInterface::setNative() */ public function setNative(mixed $native): FrameInterface { $this->native = $native; return $this; } /** * {@inheritdoc} * * @see DriverInterface::native() */ public function native(): Imagick { return $this->native; } /** * {@inheritdoc} * * @see DriverInterface::size() */ public function size(): SizeInterface { return new Rectangle( $this->native->getImageWidth(), $this->native->getImageHeight() ); } /** * {@inheritdoc} * * @see DriverInterface::delay() */ public function delay(): float { return $this->native->getImageDelay() / 100; } /** * {@inheritdoc} * * @see DriverInterface::setDelay() */ public function setDelay(float $delay): FrameInterface { $this->native->setImageDelay(intval(round($delay * 100))); return $this; } /** * {@inheritdoc} * * @see DriverInterface::dispose() */ public function dispose(): int { return $this->native->getImageDispose(); } /** * {@inheritdoc} * * @see DriverInterface::setDispose() * * @throws InputException */ public function setDispose(int $dispose): FrameInterface { if (!in_array($dispose, [0, 1, 2, 3])) { throw new InputException('Value for argument $dispose must be 0, 1, 2 or 3.'); } $this->native->setImageDispose($dispose); return $this; } /** * {@inheritdoc} * * @see DriverInterface::setOffset() */ public function setOffset(int $left, int $top): FrameInterface { $this->native->setImagePage( $this->native->getImageWidth(), $this->native->getImageHeight(), $left, $top ); return $this; } /** * {@inheritdoc} * * @see DriverInterface::offsetLeft() */ public function offsetLeft(): int { return $this->native->getImagePage()['x']; } /** * {@inheritdoc} * * @see DriverInterface::setOffsetLeft() */ public function setOffsetLeft(int $offset): FrameInterface { return $this->setOffset($offset, $this->offsetTop()); } /** * {@inheritdoc} * * @see DriverInterface::offsetTop() */ public function offsetTop(): int { return $this->native->getImagePage()['y']; } /** * {@inheritdoc} * * @see DriverInterface::setOffsetTop() */ public function setOffsetTop(int $offset): FrameInterface { return $this->setOffset($this->offsetLeft(), $offset); } } ================================================ FILE: src/Drivers/Imagick/Modifiers/AlignRotationModifier.php ================================================ core()->native()->getImageOrientation()) { case Imagick::ORIENTATION_TOPRIGHT: // 2 $image->core()->native()->flopImage(); break; case Imagick::ORIENTATION_BOTTOMRIGHT: // 3 $image->core()->native()->rotateImage("#000", 180); break; case Imagick::ORIENTATION_BOTTOMLEFT: // 4 $image->core()->native()->rotateImage("#000", 180); $image->core()->native()->flopImage(); break; case Imagick::ORIENTATION_LEFTTOP: // 5 $image->core()->native()->rotateImage("#000", -270); $image->core()->native()->flopImage(); break; case Imagick::ORIENTATION_RIGHTTOP: // 6 $image->core()->native()->rotateImage("#000", -270); break; case Imagick::ORIENTATION_RIGHTBOTTOM: // 7 $image->core()->native()->rotateImage("#000", -90); $image->core()->native()->flopImage(); break; case Imagick::ORIENTATION_LEFTBOTTOM: // 8 $image->core()->native()->rotateImage("#000", -90); break; } // set new orientation in image $image->core()->native()->setImageOrientation(Imagick::ORIENTATION_TOPLEFT); return $image; } } ================================================ FILE: src/Drivers/Imagick/Modifiers/BlendTransparencyModifier.php ================================================ blendingColor($this->driver()); // get imagickpixel from blending color $pixel = $this->driver() ->colorProcessor($image->colorspace()) ->colorToNative($blendingColor); // merge transparent areas with the background color foreach ($image as $frame) { $frame->native()->setImageBackgroundColor($pixel); $frame->native()->setImageAlphaChannel(Imagick::ALPHACHANNEL_REMOVE); $frame->native()->mergeImageLayers(Imagick::LAYERMETHOD_FLATTEN); } return $image; } } ================================================ FILE: src/Drivers/Imagick/Modifiers/BlurModifier.php ================================================ native()->blurImage($this->amount, 0.5 * $this->amount); } return $image; } } ================================================ FILE: src/Drivers/Imagick/Modifiers/BrightnessModifier.php ================================================ native()->modulateImage(100 + $this->level, 100, 100); } return $image; } } ================================================ FILE: src/Drivers/Imagick/Modifiers/ColorizeModifier.php ================================================ normalizeLevel($this->red); $green = $this->normalizeLevel($this->green); $blue = $this->normalizeLevel($this->blue); foreach ($image as $frame) { $qrange = $frame->native()->getQuantumRange(); $frame->native()->levelImage(0, $red, $qrange['quantumRangeLong'], Imagick::CHANNEL_RED); $frame->native()->levelImage(0, $green, $qrange['quantumRangeLong'], Imagick::CHANNEL_GREEN); $frame->native()->levelImage(0, $blue, $qrange['quantumRangeLong'], Imagick::CHANNEL_BLUE); } return $image; } private function normalizeLevel(int $level): int { return $level > 0 ? intval(round($level / 5)) : intval(round(($level + 100) / 100)); } } ================================================ FILE: src/Drivers/Imagick/Modifiers/ColorspaceModifier.php ================================================ */ protected static array $mapping = [ RgbColorspace::class => Imagick::COLORSPACE_SRGB, CmykColorspace::class => Imagick::COLORSPACE_CMYK, ]; public function apply(ImageInterface $image): ImageInterface { $colorspace = $this->targetColorspace(); $imagick = $image->core()->native(); $imagick->transformImageColorspace( $this->getImagickColorspace($colorspace) ); return $image; } /** * @throws NotSupportedException */ private function getImagickColorspace(ColorspaceInterface $colorspace): int { if (!array_key_exists($colorspace::class, self::$mapping)) { throw new NotSupportedException('Given colorspace is not supported.'); } return self::$mapping[$colorspace::class]; } } ================================================ FILE: src/Drivers/Imagick/Modifiers/ContainModifier.php ================================================ getCropSize($image); $resize = $this->getResizeSize($image); $transparent = new ImagickPixel('transparent'); $background = $this->driver()->colorProcessor($image->colorspace())->colorToNative( $this->driver()->handleInput($this->background) ); foreach ($image as $frame) { $frame->native()->scaleImage( $crop->width(), $crop->height(), ); $frame->native()->setBackgroundColor($transparent); $frame->native()->setImageBackgroundColor($transparent); $frame->native()->extentImage( $resize->width(), $resize->height(), $crop->pivot()->x() * -1, $crop->pivot()->y() * -1 ); if ($resize->width() > $crop->width()) { // fill new emerged background $draw = new ImagickDraw(); $draw->setFillColor($background); $delta = abs($crop->pivot()->x()); if ($delta > 0) { $draw->rectangle( 0, 0, $delta - 1, $resize->height() ); } $draw->rectangle( $crop->width() + $delta, 0, $resize->width(), $resize->height() ); $frame->native()->drawImage($draw); } if ($resize->height() > $crop->height()) { // fill new emerged background $draw = new ImagickDraw(); $draw->setFillColor($background); $delta = abs($crop->pivot()->y()); if ($delta > 0) { $draw->rectangle( 0, 0, $resize->width(), $delta - 1 ); } $draw->rectangle( 0, $crop->height() + $delta, $resize->width(), $resize->height() ); $frame->native()->drawImage($draw); } } return $image; } } ================================================ FILE: src/Drivers/Imagick/Modifiers/ContrastModifier.php ================================================ native()->sigmoidalContrastImage($this->level > 0, abs($this->level / 4), 0); } return $image; } } ================================================ FILE: src/Drivers/Imagick/Modifiers/CoverDownModifier.php ================================================ resizeDown($this->width, $this->height); } } ================================================ FILE: src/Drivers/Imagick/Modifiers/CoverModifier.php ================================================ getCropSize($image); $resize = $this->getResizeSize($crop); foreach ($image as $frame) { $frame->native()->cropImage( $crop->width(), $crop->height(), $crop->pivot()->x(), $crop->pivot()->y() ); $frame->native()->scaleImage( $resize->width(), $resize->height() ); $frame->native()->setImagePage(0, 0, 0, 0); } return $image; } } ================================================ FILE: src/Drivers/Imagick/Modifiers/CropModifier.php ================================================ driver()->colorProcessor($image->colorspace())->colorToNative( $this->driver()->handleInput($this->background) ); // create empty container imagick to rebuild core $imagick = new Imagick(); // save resolution to add it later $resolution = $image->resolution()->perInch(); // define position of the image on the new canvas $crop = $this->crop($image); $position = [ ($crop->pivot()->x() + $this->offset_x) * -1, ($crop->pivot()->y() + $this->offset_y) * -1, ]; foreach ($image as $frame) { // create new frame canvas with modifiers background $canvas = new Imagick(); $canvas->newImage($crop->width(), $crop->height(), $background, 'png'); $canvas->setImageResolution($resolution->x(), $resolution->y()); $canvas->setImageAlphaChannel(Imagick::ALPHACHANNEL_SET); // or ALPHACHANNEL_ACTIVATE? // set animation details if ($image->isAnimated()) { $canvas->setImageDelay($frame->native()->getImageDelay()); $canvas->setImageIterations($frame->native()->getImageIterations()); $canvas->setImageDispose($frame->native()->getImageDispose()); } // make the rectangular position of the original image transparent // so that we can later place the original on top. this preserves // the transparency of the original and shows the background color // of the modifier in the other areas. if the original image has no // transparent area the rectangular transparency will be covered by // the original. $clearer = new Imagick(); $clearer->newImage( $frame->native()->getImageWidth(), $frame->native()->getImageHeight(), new ImagickPixel('black'), ); $canvas->compositeImage($clearer, Imagick::COMPOSITE_DSTOUT, ...$position); // place original frame content onto prepared frame canvas $canvas->compositeImage($frame->native(), Imagick::COMPOSITE_DEFAULT, ...$position); // add newly built frame to container imagick $imagick->addImage($canvas); } // replace imagick in the original image $image->core()->setNative($imagick); return $image; } } ================================================ FILE: src/Drivers/Imagick/Modifiers/DrawBezierModifier.php ================================================ drawable->count() !== 3 && $this->drawable->count() !== 4) { throw new GeometryException('You must specify either 3 or 4 points to create a bezier curve'); } $drawing = new ImagickDraw(); if ($this->drawable->hasBackgroundColor()) { $background_color = $this->driver()->colorProcessor($image->colorspace())->colorToNative( $this->backgroundColor() ); } else { $background_color = 'transparent'; } $drawing->setFillColor($background_color); if ($this->drawable->hasBorder() && $this->drawable->borderSize() > 0) { $border_color = $this->driver()->colorProcessor($image->colorspace())->colorToNative( $this->borderColor() ); $drawing->setStrokeColor($border_color); $drawing->setStrokeWidth($this->drawable->borderSize()); } $drawing->pathStart(); $drawing->pathMoveToAbsolute( $this->drawable->first()->x(), $this->drawable->first()->y() ); if ($this->drawable->count() === 3) { $drawing->pathCurveToQuadraticBezierAbsolute( $this->drawable->second()->x(), $this->drawable->second()->y(), $this->drawable->last()->x(), $this->drawable->last()->y() ); } elseif ($this->drawable->count() === 4) { $drawing->pathCurveToAbsolute( $this->drawable->second()->x(), $this->drawable->second()->y(), $this->drawable->third()->x(), $this->drawable->third()->y(), $this->drawable->last()->x(), $this->drawable->last()->y() ); } $drawing->pathFinish(); foreach ($image as $frame) { $frame->native()->drawImage($drawing); } return $image; } } ================================================ FILE: src/Drivers/Imagick/Modifiers/DrawEllipseModifier.php ================================================ driver()->colorProcessor($image->colorspace())->colorToNative( $this->backgroundColor() ); $border_color = $this->driver()->colorProcessor($image->colorspace())->colorToNative( $this->borderColor() ); foreach ($image as $frame) { $drawing = new ImagickDraw(); $drawing->setFillColor($background_color); if ($this->drawable->hasBorder()) { $drawing->setStrokeWidth($this->drawable->borderSize()); $drawing->setStrokeColor($border_color); } $drawing->ellipse( $this->drawable->position()->x(), $this->drawable->position()->y(), $this->drawable->width() / 2, $this->drawable->height() / 2, 0, 360 ); $frame->native()->drawImage($drawing); } return $image; } } ================================================ FILE: src/Drivers/Imagick/Modifiers/DrawLineModifier.php ================================================ setStrokeWidth($this->drawable->width()); $drawing->setFillOpacity(0); $drawing->setStrokeColor( $this->driver()->colorProcessor($image->colorspace())->colorToNative( $this->backgroundColor() ) ); $drawing->line( $this->drawable->start()->x(), $this->drawable->start()->y(), $this->drawable->end()->x(), $this->drawable->end()->y(), ); foreach ($image as $frame) { $frame->native()->drawImage($drawing); } return $image; } } ================================================ FILE: src/Drivers/Imagick/Modifiers/DrawPixelModifier.php ================================================ driver()->colorProcessor($image->colorspace())->colorToNative( $this->driver()->handleInput($this->color) ); $pixel = new ImagickDraw(); $pixel->setFillColor($color); $pixel->point($this->position->x(), $this->position->y()); foreach ($image as $frame) { $frame->native()->drawImage($pixel); } return $image; } } ================================================ FILE: src/Drivers/Imagick/Modifiers/DrawPolygonModifier.php ================================================ setFillColor(new ImagickPixel('transparent')); // defaults to no backgroundColor if ($this->drawable->hasBackgroundColor()) { $background_color = $this->driver()->colorProcessor($image->colorspace())->colorToNative( $this->backgroundColor() ); $drawing->setFillColor($background_color); } if ($this->drawable->hasBorder()) { $border_color = $this->driver()->colorProcessor($image->colorspace())->colorToNative( $this->borderColor() ); $drawing->setStrokeColor($border_color); $drawing->setStrokeWidth($this->drawable->borderSize()); } $drawing->polygon($this->points()); foreach ($image as $frame) { $frame->native()->drawImage($drawing); } return $image; } /** * Return points of drawable in processable form for ImagickDraw * * @return array> */ private function points(): array { $points = []; foreach ($this->drawable as $point) { $points[] = ['x' => $point->x(), 'y' => $point->y()]; } return $points; } } ================================================ FILE: src/Drivers/Imagick/Modifiers/DrawRectangleModifier.php ================================================ driver()->colorProcessor($image->colorspace())->colorToNative( $this->backgroundColor() ); $border_color = $this->driver()->colorProcessor($image->colorspace())->colorToNative( $this->borderColor() ); $drawing->setFillColor($background_color); if ($this->drawable->hasBorder()) { $drawing->setStrokeColor($border_color); $drawing->setStrokeWidth($this->drawable->borderSize()); } // build rectangle $drawing->rectangle( $this->drawable->position()->x(), $this->drawable->position()->y(), $this->drawable->position()->x() + $this->drawable->width(), $this->drawable->position()->y() + $this->drawable->height() ); foreach ($image as $frame) { $frame->native()->drawImage($drawing); } return $image; } } ================================================ FILE: src/Drivers/Imagick/Modifiers/FillModifier.php ================================================ driver()->colorProcessor($image->colorspace())->colorToNative( $this->driver()->handleInput($this->color) ); foreach ($image->core()->native() as $frame) { if ($this->hasPosition()) { $this->floodFillWithColor($frame, $pixel); } else { $this->fillAllWithColor($frame, $pixel); } } return $image; } private function floodFillWithColor(Imagick $frame, ImagickPixel $pixel): void { $target = $frame->getImagePixelColor( $this->position->x(), $this->position->y() ); $frame->floodfillPaintImage( $pixel, 100, $target, $this->position->x(), $this->position->y(), false, Imagick::CHANNEL_ALL ); } private function fillAllWithColor(Imagick $frame, ImagickPixel $pixel): void { $draw = new ImagickDraw(); $draw->setFillColor($pixel); $draw->rectangle(0, 0, $frame->getImageWidth(), $frame->getImageHeight()); $frame->drawImage($draw); // deactive alpha channel when image was filled with opaque color if ($pixel->getColorValue(Imagick::COLOR_ALPHA) == 1) { $frame->setImageAlphaChannel(Imagick::ALPHACHANNEL_DEACTIVATE); } } } ================================================ FILE: src/Drivers/Imagick/Modifiers/FlipModifier.php ================================================ native()->flipImage(); } return $image; } } ================================================ FILE: src/Drivers/Imagick/Modifiers/FlopModifier.php ================================================ native()->flopImage(); } return $image; } } ================================================ FILE: src/Drivers/Imagick/Modifiers/GammaModifier.php ================================================ native()->gammaImage($this->gamma); } return $image; } } ================================================ FILE: src/Drivers/Imagick/Modifiers/GreyscaleModifier.php ================================================ native()->modulateImage(100, 0, 100); } return $image; } } ================================================ FILE: src/Drivers/Imagick/Modifiers/InvertModifier.php ================================================ native()->negateImage(false); } return $image; } } ================================================ FILE: src/Drivers/Imagick/Modifiers/PadModifier.php ================================================ size() ->containMax( $this->width, $this->height ) ->alignPivotTo( $this->getResizeSize($image), $this->position ); } } ================================================ FILE: src/Drivers/Imagick/Modifiers/PixelateModifier.php ================================================ pixelateFrame($frame); } return $image; } protected function pixelateFrame(FrameInterface $frame): void { $size = $frame->size(); $frame->native()->scaleImage( (int) round(max(1, $size->width() / $this->size)), (int) round(max(1, $size->height() / $this->size)) ); $frame->native()->scaleImage($size->width(), $size->height()); } } ================================================ FILE: src/Drivers/Imagick/Modifiers/PlaceModifier.php ================================================ driver()->handleInput($this->element); $position = $this->getPosition($image, $watermark); // set opacity of watermark if ($this->opacity < 100) { $watermark->core()->native()->setImageAlphaChannel(Imagick::ALPHACHANNEL_SET); $watermark->core()->native()->evaluateImage( Imagick::EVALUATE_DIVIDE, $this->opacity > 0 ? 100 / $this->opacity : 1000, Imagick::CHANNEL_ALPHA, ); } foreach ($image as $frame) { $frame->native()->compositeImage( $watermark->core()->native(), Imagick::COMPOSITE_DEFAULT, $position->x(), $position->y() ); } return $image; } } ================================================ FILE: src/Drivers/Imagick/Modifiers/ProfileModifier.php ================================================ core()->native(); $result = $imagick->profileImage('icc', (string) $this->profile); if ($result === false) { throw new ColorException('ICC color profile could not be set.'); } return $image; } } ================================================ FILE: src/Drivers/Imagick/Modifiers/ProfileRemovalModifier.php ================================================ core()->native(); $result = $imagick->profileImage('icc', null); if ($result === false) { throw new ColorException('ICC color profile could not be removed.'); } return $image; } } ================================================ FILE: src/Drivers/Imagick/Modifiers/QuantizeColorsModifier.php ================================================ limit <= 0) { throw new InputException('Quantization limit must be greater than 0.'); } // no color reduction if the limit is higher than the colors in the img if ($this->limit > $image->core()->native()->getImageColors()) { return $image; } foreach ($image as $frame) { $frame->native()->quantizeImage( $this->limit, $frame->native()->getImageColorspace(), 0, false, false ); } return $image; } } ================================================ FILE: src/Drivers/Imagick/Modifiers/RemoveAnimationModifier.php ================================================ selectedFrame($image); $imagick->addImage($frame->native()->getImage()); // set new imagick to image $image->core()->setNative($imagick); return $image; } } ================================================ FILE: src/Drivers/Imagick/Modifiers/ResizeCanvasModifier.php ================================================ cropSize($image); $image->modify(new CropModifier( $cropSize->width(), $cropSize->height(), $cropSize->pivot()->x(), $cropSize->pivot()->y(), $this->background, )); return $image; } } ================================================ FILE: src/Drivers/Imagick/Modifiers/ResizeCanvasRelativeModifier.php ================================================ size()->resizeDown($this->width, $this->height); } } ================================================ FILE: src/Drivers/Imagick/Modifiers/ResizeModifier.php ================================================ getAdjustedSize($image); foreach ($image as $frame) { $frame->native()->scaleImage( $resizeTo->width(), $resizeTo->height() ); } return $image; } /** * @throws RuntimeException */ protected function getAdjustedSize(ImageInterface $image): SizeInterface { return $image->size()->resize($this->width, $this->height); } } ================================================ FILE: src/Drivers/Imagick/Modifiers/ResolutionModifier.php ================================================ core()->native(); $imagick->setImageResolution($this->x, $this->y); return $image; } } ================================================ FILE: src/Drivers/Imagick/Modifiers/RotateModifier.php ================================================ driver()->colorProcessor($image->colorspace())->colorToNative( $this->driver()->handleInput($this->background) ); foreach ($image as $frame) { $frame->native()->rotateImage( $background, $this->rotationAngle() * -1 ); } return $image; } } ================================================ FILE: src/Drivers/Imagick/Modifiers/ScaleDownModifier.php ================================================ size()->scaleDown($this->width, $this->height); } } ================================================ FILE: src/Drivers/Imagick/Modifiers/ScaleModifier.php ================================================ size()->scale($this->width, $this->height); } } ================================================ FILE: src/Drivers/Imagick/Modifiers/SharpenModifier.php ================================================ native()->unsharpMaskImage(1, 1, $this->amount / 6.25, 0); } return $image; } } ================================================ FILE: src/Drivers/Imagick/Modifiers/SliceAnimationModifier.php ================================================ offset >= $image->count()) { throw new AnimationException('Offset is not in the range of frames.'); } $image->core()->slice($this->offset, $this->length); return $image; } } ================================================ FILE: src/Drivers/Imagick/Modifiers/StripMetaModifier.php ================================================ core()->native()->getImageProfiles('icc'); // remove meta data $image->core()->native()->stripImage(); $image->setExif(new Collection()); if ($profiles !== []) { // re-apply icc profiles $image->core()->native()->profileImage("icc", $profiles['icc']); } return $image; } } ================================================ FILE: src/Drivers/Imagick/Modifiers/TextModifier.php ================================================ processor()->textBlock($this->text, $this->font, $this->position); $drawText = $this->imagickDrawText($image, $this->font); $drawStroke = $this->imagickDrawStroke($image, $this->font); foreach ($image as $frame) { foreach ($lines as $line) { foreach ($this->strokeOffsets($this->font) as $offset) { // Draw the stroke outline under the actual text $this->maybeDrawTextline($frame, $line, $drawStroke, $offset); } // Draw the actual text $this->maybeDrawTextline($frame, $line, $drawText); } } return $image; } /** * Create an ImagickDraw object to draw text on the image * * @throws RuntimeException * @throws ColorException * @throws FontException * @throws ImagickDrawException * @throws ImagickException */ private function imagickDrawText(ImageInterface $image, FontInterface $font): ImagickDraw { $color = $this->driver()->handleInput($font->color()); if ($font->hasStrokeEffect() && $color->isTransparent()) { throw new ColorException( 'The text color must be fully opaque when using the stroke effect.' ); } $color = $this->driver()->colorProcessor($image->colorspace())->colorToNative($color); return $this->processor()->toImagickDraw($font, $color); } /** * Create a ImagickDraw object to draw the outline stroke effect on the Image * * @throws RuntimeException * @throws ColorException * @throws FontException * @throws ImagickDrawException * @throws ImagickException */ private function imagickDrawStroke(ImageInterface $image, FontInterface $font): ?ImagickDraw { if (!$font->hasStrokeEffect()) { return null; } $color = $this->driver()->handleInput($font->strokeColor()); if ($color->isTransparent()) { throw new ColorException( 'The stroke color must be fully opaque.' ); } $color = $this->driver()->colorProcessor($image->colorspace())->colorToNative($color); return $this->processor()->toImagickDraw($font, $color); } /** * Maybe draw given line of text on frame instance depending on given * ImageDraw instance. Optionally move line position by given offset. */ private function maybeDrawTextline( FrameInterface $frame, Line $textline, ?ImagickDraw $draw = null, PointInterface $offset = new Point(), ): void { if ($draw instanceof ImagickDraw) { $frame->native()->annotateImage( $draw, $textline->position()->x() + $offset->x(), $textline->position()->y() + $offset->y(), $this->font->angle(), (string) $textline ); } } /** * Return imagick font processor * * @throws FontException */ private function processor(): FontProcessor { $processor = $this->driver()->fontProcessor(); if (!($processor instanceof FontProcessor)) { throw new FontException('Font processor does not match the driver.'); } return $processor; } } ================================================ FILE: src/Drivers/Imagick/Modifiers/TrimModifier.php ================================================ isAnimated()) { throw new NotSupportedException('Trim modifier cannot be applied to animated images.'); } $imagick = $image->core()->native(); $imagick->trimImage(($this->tolerance / 100 * $imagick->getQuantum()) / 1.5); $imagick->setImagePage(0, 0, 0, 0); return $image; } } ================================================ FILE: src/Drivers/Specializable.php ================================================ analyze($this); } } ================================================ FILE: src/Drivers/SpecializableDecoder.php ================================================ modify($this); } } ================================================ FILE: src/EncodedImage.php ================================================ mediaType; } /** * {@inheritdoc} * * @see EncodedImageInterface::mimetype() */ public function mimetype(): string { return $this->mediaType(); } /** * {@inheritdoc} * * @see EncodedImageInterface::toDataUri() */ public function toDataUri(): string { return sprintf('data:%s;base64,%s', $this->mediaType(), base64_encode((string) $this)); } /** * Show debug info for the current image * * @return array */ public function __debugInfo(): array { return [ 'mediaType' => $this->mediaType(), 'size' => $this->size(), ]; } } ================================================ FILE: src/Encoders/AutoEncoder.php ================================================ encode( $this->encoderByMediaType( $image->origin()->mediaType() ) ); } } ================================================ FILE: src/Encoders/AvifEncoder.php ================================================ */ protected array $options = []; /** * Create new encoder instance to encode to format of given file extension * * @param null|string|FileExtension $extension Target file extension for example "png" * @return void */ public function __construct(public null|string|FileExtension $extension = null, mixed ...$options) { $this->options = $options; } /** * {@inheritdoc} * * @see EncoderInterface::encode() */ public function encode(ImageInterface $image): EncodedImageInterface { $extension = is_null($this->extension) ? $image->origin()->fileExtension() : $this->extension; return $image->encode( $this->encoderByFileExtension( $extension ) ); } /** * Create matching encoder for given file extension * * @throws EncoderException */ protected function encoderByFileExtension(null|string|FileExtension $extension): EncoderInterface { if (empty($extension)) { throw new EncoderException('No encoder found for empty file extension.'); } try { $extension = is_string($extension) ? FileExtension::from(strtolower($extension)) : $extension; } catch (Error) { throw new EncoderException('No encoder found for file extension (' . $extension . ').'); } return $extension->format()->encoder(...$this->options); } } ================================================ FILE: src/Encoders/FilePathEncoder.php ================================================ encode( $this->encoderByFileExtension( is_null($this->path) ? $image->origin()->fileExtension() : pathinfo($this->path, PATHINFO_EXTENSION) ) ); } } ================================================ FILE: src/Encoders/GifEncoder.php ================================================ */ protected array $options = []; /** * Create new encoder instance * * @param null|string|MediaType $mediaType Target media type for example "image/jpeg" * @return void */ public function __construct(public null|string|MediaType $mediaType = null, mixed ...$options) { $this->options = $options; } /** * {@inheritdoc} * * @see EncoderInterface::encode() */ public function encode(ImageInterface $image): EncodedImageInterface { $mediaType = is_null($this->mediaType) ? $image->origin()->mediaType() : $this->mediaType; return $image->encode( $this->encoderByMediaType($mediaType) ); } /** * Return new encoder by given media (MIME) type * * @throws EncoderException */ protected function encoderByMediaType(string|MediaType $mediaType): EncoderInterface { try { $mediaType = is_string($mediaType) ? MediaType::from($mediaType) : $mediaType; } catch (Error) { throw new EncoderException('No encoder found for media type (' . $mediaType . ').'); } return $mediaType->format()->encoder(...$this->options); } } ================================================ FILE: src/Encoders/PngEncoder.php ================================================ pointer = $this->buildFilePointer($data); } /** * Create file object from path in file system * * @throws RuntimeException */ public static function fromPath(string $path): self { return new self(fopen($path, 'r')); } /** * {@inheritdoc} * * @see FileInterface::save() */ public function save(string $filepath): void { $dir = pathinfo($filepath, PATHINFO_DIRNAME); if (!is_dir($dir)) { throw new NotWritableException( "Can't write image to path. Directory does not exist." ); } if (!is_writable($dir)) { throw new NotWritableException( "Can't write image to path. Directory is not writable." ); } if (is_file($filepath) && !is_writable($filepath)) { throw new NotWritableException( sprintf("Can't write image. Path (%s) is not writable.", $filepath) ); } // write data $saved = @file_put_contents($filepath, $this->toFilePointer()); if ($saved === false) { throw new NotWritableException( sprintf("Can't write image data to path (%s).", $filepath) ); } } /** * {@inheritdoc} * * @see FileInterface::toString() */ public function toString(): string { return stream_get_contents($this->toFilePointer(), offset: 0); } /** * {@inheritdoc} * * @see FileInterface::toFilePointer() */ public function toFilePointer() { rewind($this->pointer); return $this->pointer; } /** * {@inheritdoc} * * @see FileInterface::size() */ public function size(): int { $info = fstat($this->toFilePointer()); return intval($info['size']); } /** * {@inheritdoc} * * @see FileInterface::__toString() */ public function __toString(): string { return $this->toString(); } } ================================================ FILE: src/FileExtension.php ================================================ fileExtension(); } if ($identifier instanceof MediaType) { return $identifier->fileExtension(); } try { $extension = self::from(strtolower($identifier)); } catch (Error) { try { $extension = MediaType::from(strtolower($identifier))->fileExtension(); } catch (Error) { throw new NotSupportedException('Unable to create file extension from "' . $identifier . '".'); } } return $extension; } /** * Try to create media type from given identifier and return null on failure * * @param string|Format|MediaType|FileExtension $identifier * @return FileExtension|null */ public static function tryCreate(string|self|Format|MediaType $identifier): ?self { try { return self::create($identifier); } catch (NotSupportedException) { return null; } } /** * Return the matching format for the current file extension */ public function format(): Format { return match ($this) { self::JPEG, self::JPG => Format::JPEG, self::WEBP => Format::WEBP, self::GIF => Format::GIF, self::PNG => Format::PNG, self::AVIF => Format::AVIF, self::BMP => Format::BMP, self::TIF, self::TIFF => Format::TIFF, self::JP2, self::JP2K, self::J2K, self::JPF, self::JPM, self::JPG2, self::J2C, self::JPC, self::JPX => Format::JP2, self::HEIC, self::HEIF => Format::HEIC, }; } /** * Return media types for the current format * * @return array */ public function mediaTypes(): array { return $this->format()->mediaTypes(); } /** * Return the first found media type for the current format */ public function mediaType(): MediaType { return $this->format()->mediaType(); } } ================================================ FILE: src/Format.php ================================================ format(); } if ($identifier instanceof FileExtension) { return $identifier->format(); } try { $format = MediaType::from(strtolower($identifier))->format(); } catch (Error) { try { $format = FileExtension::from(strtolower($identifier))->format(); } catch (Error) { throw new NotSupportedException('Unable to create format from "' . $identifier . '".'); } } return $format; } /** * Try to create format from given identifier and return null on failure * * @param string|Format|MediaType|FileExtension $identifier * @return Format|null */ public static function tryCreate(string|self|MediaType|FileExtension $identifier): ?self { try { return self::create($identifier); } catch (NotSupportedException) { return null; } } /** * Return the possible media (MIME) types for the current format * * @return array */ public function mediaTypes(): array { return array_filter( MediaType::cases(), fn(MediaType $mediaType): bool => $mediaType->format() === $this ); } /** * Return the first found media type for the current format */ public function mediaType(): MediaType { $types = $this->mediaTypes(); return reset($types); } /** * Return the possible file extension for the current format * * @return array */ public function fileExtensions(): array { return array_filter( FileExtension::cases(), fn(FileExtension $fileExtension): bool => $fileExtension->format() === $this ); } /** * Return the first found file extension for the current format */ public function fileExtension(): FileExtension { $extensions = $this->fileExtensions(); return reset($extensions); } /** * Create an encoder instance with given options that matches the format */ public function encoder(mixed ...$options): EncoderInterface { // get classname of target encoder from current format $classname = match ($this) { self::AVIF => AvifEncoder::class, self::BMP => BmpEncoder::class, self::GIF => GifEncoder::class, self::HEIC => HeicEncoder::class, self::JP2 => Jpeg2000Encoder::class, self::JPEG => JpegEncoder::class, self::PNG => PngEncoder::class, self::TIFF => TiffEncoder::class, self::WEBP => WebpEncoder::class, }; // get parameters of target encoder $parameters = []; $reflectionClass = new ReflectionClass($classname); if ($constructor = $reflectionClass->getConstructor()) { $parameters = array_map( fn(ReflectionParameter $parameter): string => $parameter->getName(), $constructor->getParameters(), ); } // filter out unavailable options of target encoder $options = array_filter( $options, fn(mixed $key): bool => in_array($key, $parameters), ARRAY_FILTER_USE_KEY, ); return new $classname(...$options); } } ================================================ FILE: src/Geometry/Bezier.php ================================================ * @implements ArrayAccess */ class Bezier implements IteratorAggregate, Countable, ArrayAccess, DrawableInterface { use HasBorder; use HasBackgroundColor; /** * Create new bezier instance * * @param array $points * @return void */ public function __construct( protected array $points = [], protected PointInterface $pivot = new Point() ) { // } /** * {@inheritdoc} * * @see DrawableInterface::position() */ public function position(): PointInterface { return $this->pivot; } /** * {@inheritdoc} * * @see DrawableInterface::setPosition() */ public function setPosition(PointInterface $position): DrawableInterface { $this->pivot = $position; return $this; } /** * Implement iteration through all points of bezier * * @return Traversable */ public function getIterator(): Traversable { return new ArrayIterator($this->points); } /** * Return current pivot point */ public function pivot(): PointInterface { return $this->pivot; } /** * Change pivot point to given point */ public function setPivot(PointInterface $pivot): self { $this->pivot = $pivot; return $this; } /** * Return first control point of bezier */ public function first(): ?PointInterface { if ($point = reset($this->points)) { return $point; } return null; } /** * Return second control point of bezier */ public function second(): ?PointInterface { if (array_key_exists(1, $this->points)) { return $this->points[1]; } return null; } /** * Return third control point of bezier */ public function third(): ?PointInterface { if (array_key_exists(2, $this->points)) { return $this->points[2]; } return null; } /** * Return last control point of bezier */ public function last(): ?PointInterface { if ($point = end($this->points)) { return $point; } return null; } /** * Return bezier's point count */ public function count(): int { return count($this->points); } /** * Determine if point exists at given offset */ public function offsetExists(mixed $offset): bool { return array_key_exists($offset, $this->points); } /** * Return point at given offset */ public function offsetGet(mixed $offset): mixed { return $this->points[$offset]; } /** * Set point at given offset */ public function offsetSet(mixed $offset, mixed $value): void { $this->points[$offset] = $value; } /** * Unset offset at given offset */ public function offsetUnset(mixed $offset): void { unset($this->points[$offset]); } /** * Add given point to bezier */ public function addPoint(PointInterface $point): self { $this->points[] = $point; return $this; } /** * Return array of all x/y values of all points of bezier * * @return array */ public function toArray(): array { $coordinates = []; foreach ($this->points as $point) { $coordinates[] = $point->x(); $coordinates[] = $point->y(); } return $coordinates; } } ================================================ FILE: src/Geometry/Circle.php ================================================ setWidth($diameter); $this->setHeight($diameter); return $this; } /** * Get diameter of circle */ public function diameter(): int { return $this->width(); } /** * Set radius of circle */ public function setRadius(int $radius): self { return $this->setDiameter(intval($radius * 2)); } /** * Get radius of circle */ public function radius(): int { return intval(round($this->diameter() / 2)); } } ================================================ FILE: src/Geometry/Ellipse.php ================================================ pivot; } /** * {@inheritdoc} * * @see DrawableInterface::setPosition() */ public function setPosition(PointInterface $position): self { $this->pivot = $position; return $this; } /** * Return pivot point of Ellipse */ public function pivot(): PointInterface { return $this->pivot; } /** * Set size of Ellipse */ public function setSize(int $width, int $height): self { return $this->setWidth($width)->setHeight($height); } /** * Set width of Ellipse */ public function setWidth(int $width): self { $this->width = $width; return $this; } /** * Set height of Ellipse */ public function setHeight(int $height): self { $this->height = $height; return $this; } /** * Get width of Ellipse */ public function width(): int { return $this->width; } /** * Get height of Ellipse */ public function height(): int { return $this->height; } } ================================================ FILE: src/Geometry/Factories/BezierFactory.php ================================================ bezier = is_a($init, Bezier::class) ? $init : new Bezier([]); if (is_callable($init)) { $init($this); } } /** * {@inheritdoc} * * @see DrawableFactoryInterface::init() */ public static function init(null|Closure|DrawableInterface $init = null): self { return new self($init); } /** * {@inheritdoc} * * @see DrawableFactoryInterface::create() */ public function create(): DrawableInterface { return $this->bezier; } /** * Add a point to the bezier to be produced */ public function point(int $x, int $y): self { $this->bezier->addPoint(new Point($x, $y)); return $this; } /** * Set the background color of the bezier to be produced */ public function background(mixed $color): self { $this->bezier->setBackgroundColor($color); return $this; } /** * Set the border color & border size of the bezier to be produced */ public function border(mixed $color, int $size = 1): self { $this->bezier->setBorder($color, $size); return $this; } /** * Produce the bezier */ public function __invoke(): Bezier { return $this->bezier; } } ================================================ FILE: src/Geometry/Factories/CircleFactory.php ================================================ circle = is_a($init, Circle::class) ? $init : new Circle(0); $this->circle->setPosition($pivot); if (is_callable($init)) { $init($this); } } /** * {@inheritdoc} * * @see DrawableFactoryInterface::init() */ public static function init(null|Closure|DrawableInterface $init = null): self { return new self(init: $init); } /** * {@inheritdoc} * * @see DrawableFactoryInterface::create() */ public function create(): DrawableInterface { return $this->circle; } /** * Set the radius of the circle to be produced */ public function radius(int $radius): self { $this->circle->setSize($radius * 2, $radius * 2); return $this; } /** * Set the diameter of the circle to be produced */ public function diameter(int $diameter): self { $this->circle->setSize($diameter, $diameter); return $this; } /** * Set the background color of the circle to be produced */ public function background(mixed $color): self { $this->circle->setBackgroundColor($color); return $this; } /** * Set the border color & border size of the ellipse to be produced */ public function border(mixed $color, int $size = 1): self { $this->circle->setBorder($color, $size); return $this; } /** * Produce the circle */ public function __invoke(): Circle { return $this->circle; } } ================================================ FILE: src/Geometry/Factories/Drawable.php ================================================ ellipse = is_a($init, Ellipse::class) ? $init : new Ellipse(0, 0); $this->ellipse->setPosition($pivot); if (is_callable($init)) { $init($this); } } /** * {@inheritdoc} * * @see DrawableFactoryInterface::init() */ public static function init(null|Closure|DrawableInterface $init = null): self { return new self(init: $init); } /** * {@inheritdoc} * * @see DrawableFactoryInterface::create() */ public function create(): DrawableInterface { return $this->ellipse; } /** * Set the size of the ellipse to be produced */ public function size(int $width, int $height): self { $this->ellipse->setSize($width, $height); return $this; } /** * Set the width of the ellipse to be produced */ public function width(int $width): self { $this->ellipse->setWidth($width); return $this; } /** * Set the height of the ellipse to be produced */ public function height(int $height): self { $this->ellipse->setHeight($height); return $this; } /** * Set the background color of the ellipse to be produced */ public function background(mixed $color): self { $this->ellipse->setBackgroundColor($color); return $this; } /** * Set the border color & border size of the ellipse to be produced */ public function border(mixed $color, int $size = 1): self { $this->ellipse->setBorder($color, $size); return $this; } /** * Produce the ellipse */ public function __invoke(): Ellipse { return $this->ellipse; } } ================================================ FILE: src/Geometry/Factories/LineFactory.php ================================================ line = is_a($init, Line::class) ? $init : new Line(new Point(), new Point()); if (is_callable($init)) { $init($this); } } /** * {@inheritdoc} * * @see DrawableFactoryInterface::init() */ public static function init(null|Closure|DrawableInterface $init = null): self { return new self($init); } /** * {@inheritdoc} * * @see DrawableFactoryInterface::create() */ public function create(): DrawableInterface { return $this->line; } /** * Set the color of the line to be produced */ public function color(mixed $color): self { $this->line->setBackgroundColor($color); $this->line->setBorderColor($color); return $this; } /** * Set the (background) color of the line to be produced */ public function background(mixed $color): self { $this->line->setBackgroundColor($color); $this->line->setBorderColor($color); return $this; } /** * Set the border size & border color of the line to be produced */ public function border(mixed $color, int $size = 1): self { $this->line->setBackgroundColor($color); $this->line->setBorderColor($color); $this->line->setWidth($size); return $this; } /** * Set the width of the line to be produced */ public function width(int $size): self { $this->line->setWidth($size); return $this; } /** * Set the coordinates of the starting point of the line to be produced */ public function from(int $x, int $y): self { $this->line->setStart(new Point($x, $y)); return $this; } /** * Set the coordinates of the end point of the line to be produced */ public function to(int $x, int $y): self { $this->line->setEnd(new Point($x, $y)); return $this; } /** * Produce the line */ public function __invoke(): Line { return $this->line; } } ================================================ FILE: src/Geometry/Factories/PolygonFactory.php ================================================ polygon = is_a($init, Polygon::class) ? $init : new Polygon([]); if (is_callable($init)) { $init($this); } } /** * {@inheritdoc} * * @see DrawableFactoryInterface::init() */ public static function init(null|Closure|DrawableInterface $init = null): self { return new self($init); } /** * {@inheritdoc} * * @see DrawableFactoryInterface::create() */ public function create(): DrawableInterface { return $this->polygon; } /** * Add a point to the polygon to be produced */ public function point(int $x, int $y): self { $this->polygon->addPoint(new Point($x, $y)); return $this; } /** * Set the background color of the polygon to be produced */ public function background(mixed $color): self { $this->polygon->setBackgroundColor($color); return $this; } /** * Set the border color & border size of the polygon to be produced */ public function border(mixed $color, int $size = 1): self { $this->polygon->setBorder($color, $size); return $this; } /** * Produce the polygon */ public function __invoke(): Polygon { return $this->polygon; } } ================================================ FILE: src/Geometry/Factories/RectangleFactory.php ================================================ rectangle = is_a($init, Rectangle::class) ? $init : new Rectangle(0, 0, $pivot); $this->rectangle->setPosition($pivot); if (is_callable($init)) { $init($this); } } /** * {@inheritdoc} * * @see DrawableFactoryInterface::init() */ public static function init(null|Closure|DrawableInterface $init = null): self { return new self(init: $init); } /** * {@inheritdoc} * * @see DrawableFactoryInterface::create() */ public function create(): DrawableInterface { return $this->rectangle; } /** * Set the size of the rectangle to be produced */ public function size(int $width, int $height): self { $this->rectangle->setSize($width, $height); return $this; } /** * Set the width of the rectangle to be produced */ public function width(int $width): self { $this->rectangle->setWidth($width); return $this; } /** * Set the height of the rectangle to be produced */ public function height(int $height): self { $this->rectangle->setHeight($height); return $this; } /** * Set the background color of the rectangle to be produced */ public function background(mixed $color): self { $this->rectangle->setBackgroundColor($color); return $this; } /** * Set the border color & border size of the rectangle to be produced */ public function border(mixed $color, int $size = 1): self { $this->rectangle->setBorder($color, $size); return $this; } /** * Produce the rectangle */ public function __invoke(): Rectangle { return $this->rectangle; } } ================================================ FILE: src/Geometry/Line.php ================================================ start; } /** * {@inheritdoc} * * @see DrawableInterface::setPosition() */ public function setPosition(PointInterface $position): DrawableInterface { $this->start = $position; return $this; } /** * Return line width */ public function width(): int { return $this->width; } /** * Set line width */ public function setWidth(int $width): self { $this->width = $width; return $this; } /** * Get starting point of line */ public function start(): PointInterface { return $this->start; } /** * get end point of line */ public function end(): PointInterface { return $this->end; } /** * Set starting point of line */ public function setStart(PointInterface $start): self { $this->start = $start; return $this; } /** * Set starting point of line by coordinates */ public function from(int $x, int $y): self { $this->start()->setX($x); $this->start()->setY($y); return $this; } /** * Set end point of line by coordinates */ public function to(int $x, int $y): self { $this->end()->setX($x); $this->end()->setY($y); return $this; } /** * Set end point of line */ public function setEnd(PointInterface $end): self { $this->end = $end; return $this; } } ================================================ FILE: src/Geometry/Pixel.php ================================================ background = $background; return $this; } /** * {@inheritdoc} * * @see DrawableInterface::backgroundColor() */ public function backgroundColor(): ColorInterface { return $this->background; } } ================================================ FILE: src/Geometry/Point.php ================================================ */ class Point implements PointInterface, IteratorAggregate { /** * Create new point instance * * @return void */ public function __construct( protected int $x = 0, protected int $y = 0 ) { // } /** * {@inheritdoc} * * @see IteratorAggregate::getIterator() */ public function getIterator(): Traversable { return new ArrayIterator([$this->x, $this->y]); } /** * {@inheritdoc} * * @see PointInterface::setX() */ public function setX(int $x): self { $this->x = $x; return $this; } /** * {@inheritdoc} * * @see PointInterface::x() */ public function x(): int { return $this->x; } /** * {@inheritdoc} * * @see PointInterface::setY() */ public function setY(int $y): self { $this->y = $y; return $this; } /** * {@inheritdoc} * * @see PointInterface::y() */ public function y(): int { return $this->y; } /** * {@inheritdoc} * * @see PointInterface::moveX() */ public function moveX(int $value): self { $this->x += $value; return $this; } /** * {@inheritdoc} * * @see PointInterface::moveY() */ public function moveY(int $value): self { $this->y += $value; return $this; } /** * {@inheritdoc} * * @see PointInterface::move() */ public function move(int $x, int $y): self { return $this->moveX($x)->moveY($y); } /** * {@inheritdoc} * * @see PointInterface::setPosition() */ public function setPosition(int $x, int $y): self { $this->setX($x); $this->setY($y); return $this; } /** * {@inheritdoc} * * @see PointInterface::rotate() */ public function rotate(float $angle, PointInterface $pivot): self { $sin = round(sin(deg2rad($angle)), 6); $cos = round(cos(deg2rad($angle)), 6); return $this->setPosition( intval($cos * ($this->x() - $pivot->x()) - $sin * ($this->y() - $pivot->y()) + $pivot->x()), intval($sin * ($this->x() - $pivot->x()) + $cos * ($this->y() - $pivot->y()) + $pivot->y()) ); } } ================================================ FILE: src/Geometry/Polygon.php ================================================ * @implements ArrayAccess */ class Polygon implements IteratorAggregate, Countable, ArrayAccess, DrawableInterface { use HasBorder; use HasBackgroundColor; /** * Create new polygon instance * * @param array $points * @return void */ public function __construct( protected array $points = [], protected PointInterface $pivot = new Point() ) { // } /** * {@inheritdoc} * * @see DrawableInterface::position() */ public function position(): PointInterface { return $this->pivot; } /** * {@inheritdoc} * * @see DrawableInterface::setPosition() */ public function setPosition(PointInterface $position): self { $this->pivot = $position; return $this; } /** * Implement iteration through all points of polygon * * @return Traversable */ public function getIterator(): Traversable { return new ArrayIterator($this->points); } /** * Return current pivot point */ public function pivot(): PointInterface { return $this->pivot; } /** * Change pivot point to given point */ public function setPivot(PointInterface $pivot): self { $this->pivot = $pivot; return $this; } /** * Return first point of polygon */ public function first(): ?PointInterface { if ($point = reset($this->points)) { return $point; } return null; } /** * Return last point of polygon */ public function last(): ?PointInterface { if ($point = end($this->points)) { return $point; } return null; } /** * Return polygon's point count */ public function count(): int { return count($this->points); } /** * Determine if point exists at given offset */ public function offsetExists(mixed $offset): bool { return array_key_exists($offset, $this->points); } /** * Return point at given offset */ public function offsetGet(mixed $offset): mixed { return $this->points[$offset]; } /** * Set point at given offset */ public function offsetSet(mixed $offset, mixed $value): void { $this->points[$offset] = $value; } /** * Unset offset at given offset */ public function offsetUnset(mixed $offset): void { unset($this->points[$offset]); } /** * Add given point to polygon */ public function addPoint(PointInterface $point): self { $this->points[] = $point; return $this; } /** * Calculate total horizontal span of polygon */ public function width(): int { return abs($this->mostLeftPoint()->x() - $this->mostRightPoint()->x()); } /** * Calculate total vertical span of polygon */ public function height(): int { return abs($this->mostBottomPoint()->y() - $this->mostTopPoint()->y()); } /** * Return most left point of all points in polygon */ public function mostLeftPoint(): PointInterface { $points = $this->points; usort($points, function (PointInterface $a, PointInterface $b): int { if ($a->x() === $b->x()) { return 0; } return $a->x() < $b->x() ? -1 : 1; }); return $points[0]; } /** * Return most right point in polygon */ public function mostRightPoint(): PointInterface { $points = $this->points; usort($points, function (PointInterface $a, PointInterface $b): int { if ($a->x() === $b->x()) { return 0; } return $a->x() > $b->x() ? -1 : 1; }); return $points[0]; } /** * Return most top point in polygon */ public function mostTopPoint(): PointInterface { $points = $this->points; usort($points, function (PointInterface $a, PointInterface $b): int { if ($a->y() === $b->y()) { return 0; } return $a->y() > $b->y() ? -1 : 1; }); return $points[0]; } /** * Return most bottom point in polygon */ public function mostBottomPoint(): PointInterface { $points = $this->points; usort($points, function (PointInterface $a, PointInterface $b): int { if ($a->y() === $b->y()) { return 0; } return $a->y() < $b->y() ? -1 : 1; }); return $points[0]; } /** * Return point in absolute center of the polygon */ public function centerPoint(): PointInterface { return new Point( $this->mostRightPoint()->x() - (intval(round($this->width() / 2))), $this->mostTopPoint()->y() - (intval(round($this->height() / 2))) ); } /** * Align all points of polygon horizontally to given position around pivot point */ public function align(string $position): self { switch (strtolower($position)) { case 'center': case 'middle': $diff = $this->centerPoint()->x() - $this->pivot()->x(); break; case 'right': $diff = $this->mostRightPoint()->x() - $this->pivot()->x(); break; default: case 'left': $diff = $this->mostLeftPoint()->x() - $this->pivot()->x(); break; } foreach ($this->points as $point) { $point->setX( intval($point->x() - $diff) ); } return $this; } /** * Align all points of polygon vertically to given position around pivot point */ public function valign(string $position): self { switch (strtolower($position)) { case 'center': case 'middle': $diff = $this->centerPoint()->y() - $this->pivot()->y(); break; case 'top': $diff = $this->mostTopPoint()->y() - $this->pivot()->y() - $this->height(); break; default: case 'bottom': $diff = $this->mostBottomPoint()->y() - $this->pivot()->y() + $this->height(); break; } foreach ($this->points as $point) { $point->setY( intval($point->y() - $diff), ); } return $this; } /** * Rotate points of polygon around pivot point with given angle */ public function rotate(float $angle): self { $sin = sin(deg2rad($angle)); $cos = cos(deg2rad($angle)); foreach ($this->points as $point) { // translate point to pivot $point->setX( intval($point->x() - $this->pivot()->x()), ); $point->setY( intval($point->y() - $this->pivot()->y()), ); // rotate point $x = $point->x() * $cos - $point->y() * $sin; $y = $point->x() * $sin + $point->y() * $cos; // translate point back $point->setX( intval($x + $this->pivot()->x()), ); $point->setY( intval($y + $this->pivot()->y()), ); } return $this; } /** * Move all points by given amount on the x-axis */ public function movePointsX(int $amount): self { foreach ($this->points as $point) { $point->moveX($amount); } return $this; } /** * Move all points by given amount on the y-axis */ public function movePointsY(int $amount): self { foreach ($this->points as $point) { $point->moveY($amount); } return $this; } /** * Return array of all x/y values of all points of polygon * * @return array */ public function toArray(): array { $coordinates = []; foreach ($this->points as $point) { $coordinates[] = $point->x(); $coordinates[] = $point->y(); } return $coordinates; } } ================================================ FILE: src/Geometry/Rectangle.php ================================================ addPoint(new Point($this->pivot->x(), $this->pivot->y())); $this->addPoint(new Point($this->pivot->x() + $width, $this->pivot->y())); $this->addPoint(new Point($this->pivot->x() + $width, $this->pivot->y() - $height)); $this->addPoint(new Point($this->pivot->x(), $this->pivot->y() - $height)); } /** * Set size of rectangle */ public function setSize(int $width, int $height): self { return $this->setWidth($width)->setHeight($height); } /** * Set width of rectangle */ public function setWidth(int $width): self { $this[1]->setX($this[0]->x() + $width); $this[2]->setX($this[3]->x() + $width); return $this; } /** * Set height of rectangle */ public function setHeight(int $height): self { $this[2]->setY($this[1]->y() + $height); $this[3]->setY($this[0]->y() + $height); return $this; } /** * Return pivot point of rectangle */ public function pivot(): PointInterface { return $this->pivot; } /** * Set pivot point of rectangle */ public function setPivot(PointInterface $pivot): self { $this->pivot = $pivot; return $this; } /** * Move pivot to the given position in the rectangle and adjust the new * position by given offset values. */ public function movePivot(string $position, int $offset_x = 0, int $offset_y = 0): self { switch (strtolower($position)) { case 'top': case 'top-center': case 'top-middle': case 'center-top': case 'middle-top': $x = intval(round($this->width() / 2)) + $offset_x; $y = $offset_y; break; case 'top-right': case 'right-top': $x = $this->width() - $offset_x; $y = $offset_y; break; case 'left': case 'left-center': case 'left-middle': case 'center-left': case 'middle-left': $x = $offset_x; $y = intval(round($this->height() / 2)) + $offset_y; break; case 'right': case 'right-center': case 'right-middle': case 'center-right': case 'middle-right': $x = $this->width() - $offset_x; $y = intval(round($this->height() / 2)) + $offset_y; break; case 'bottom-left': case 'left-bottom': $x = $offset_x; $y = $this->height() - $offset_y; break; case 'bottom': case 'bottom-center': case 'bottom-middle': case 'center-bottom': case 'middle-bottom': $x = intval(round($this->width() / 2)) + $offset_x; $y = $this->height() - $offset_y; break; case 'bottom-right': case 'right-bottom': $x = $this->width() - $offset_x; $y = $this->height() - $offset_y; break; case 'center': case 'middle': case 'center-center': case 'middle-middle': $x = intval(round($this->width() / 2)) + $offset_x; $y = intval(round($this->height() / 2)) + $offset_y; break; default: case 'top-left': case 'left-top': $x = $offset_x; $y = $offset_y; break; } $this->pivot->setPosition($x, $y); return $this; } /** * Align pivot relative to given size at given position */ public function alignPivotTo(SizeInterface $size, string $position): self { $reference = new self($size->width(), $size->height()); $reference->movePivot($position); $this->movePivot($position)->setPivot( $reference->relativePositionTo($this) ); return $this; } /** * Return relative position to given rectangle */ public function relativePositionTo(SizeInterface $rectangle): PointInterface { return new Point( $this->pivot()->x() - $rectangle->pivot()->x(), $this->pivot()->y() - $rectangle->pivot()->y() ); } /** * Return aspect ration of rectangle */ public function aspectRatio(): float { return $this->width() / $this->height(); } /** * Determine if rectangle fits into given rectangle */ public function fitsInto(SizeInterface $size): bool { if ($this->width() > $size->width()) { return false; } if ($this->height() > $size->height()) { return false; } return true; } /** * Determine if rectangle has landscape format */ public function isLandscape(): bool { return $this->width() > $this->height(); } /** * Determine if rectangle has landscape format */ public function isPortrait(): bool { return $this->width() < $this->height(); } /** * Return most top left point of rectangle */ public function topLeftPoint(): PointInterface { return $this->points[0]; } /** * Return bottom right point of rectangle */ public function bottomRightPoint(): PointInterface { return $this->points[2]; } /** * @see SizeInterface::resize() * * @throws GeometryException */ public function resize(?int $width = null, ?int $height = null): SizeInterface { return $this->resizer($width, $height)->resize($this); } /** * @see SizeInterface::resizeDown() * * @throws GeometryException */ public function resizeDown(?int $width = null, ?int $height = null): SizeInterface { return $this->resizer($width, $height)->resizeDown($this); } /** * @see SizeInterface::scale() * * @throws GeometryException */ public function scale(?int $width = null, ?int $height = null): SizeInterface { return $this->resizer($width, $height)->scale($this); } /** * @see SizeInterface::scaleDown() * * @throws GeometryException */ public function scaleDown(?int $width = null, ?int $height = null): SizeInterface { return $this->resizer($width, $height)->scaleDown($this); } /** * @see SizeInterface::cover() * * @throws GeometryException */ public function cover(int $width, int $height): SizeInterface { return $this->resizer($width, $height)->cover($this); } /** * @see SizeInterface::contain() * * @throws GeometryException */ public function contain(int $width, int $height): SizeInterface { return $this->resizer($width, $height)->contain($this); } /** * @see SizeInterface::containMax() * * @throws GeometryException */ public function containMax(int $width, int $height): SizeInterface { return $this->resizer($width, $height)->containDown($this); } /** * Create resizer instance with given target size * * @throws GeometryException */ protected function resizer(?int $width = null, ?int $height = null): RectangleResizer { return new RectangleResizer($width, $height); } /** * Show debug info for the current rectangle * * @return array */ public function __debugInfo(): array { return [ 'width' => $this->width(), 'height' => $this->height(), 'pivot' => $this->pivot, ]; } } ================================================ FILE: src/Geometry/Tools/RectangleResizer.php ================================================ width); } /** * Return target width of resizer if available */ protected function getTargetWidth(): ?int { return $this->hasTargetWidth() ? $this->width : null; } /** * Determine if resize has target height */ protected function hasTargetHeight(): bool { return is_int($this->height); } /** * Return target width of resizer if available */ protected function getTargetHeight(): ?int { return $this->hasTargetHeight() ? $this->height : null; } /** * Return target size object * * @throws GeometryException */ protected function getTargetSize(): SizeInterface { if (!$this->hasTargetWidth() || !$this->hasTargetHeight()) { throw new GeometryException('Target size needs width and height.'); } return new Rectangle($this->width, $this->height); } /** * Set target width of resizer */ public function toWidth(int $width): self { $this->width = $width; return $this; } /** * Set target height of resizer */ public function toHeight(int $height): self { $this->height = $height; return $this; } /** * Set target size to given size object */ public function toSize(SizeInterface $size): self { $this->width = $size->width(); $this->height = $size->height(); return $this; } /** * Get proportinal width */ protected function getProportionalWidth(SizeInterface $size): int { if (!$this->hasTargetHeight()) { return $size->width(); } return max([1, (int) round($this->height * $size->aspectRatio())]); } /** * Get proportinal height */ protected function getProportionalHeight(SizeInterface $size): int { if (!$this->hasTargetWidth()) { return $size->height(); } return max([1, (int) round($this->width / $size->aspectRatio())]); } /** * Resize given size to target size of the resizer */ public function resize(SizeInterface $size): SizeInterface { $resized = new Rectangle($size->width(), $size->height()); if ($width = $this->getTargetWidth()) { $resized->setWidth($width); } if ($height = $this->getTargetHeight()) { $resized->setHeight($height); } return $resized; } /** * Resize given size to target size of the resizer but do not exceed original size */ public function resizeDown(SizeInterface $size): SizeInterface { $resized = new Rectangle($size->width(), $size->height()); if ($width = $this->getTargetWidth()) { $resized->setWidth( min($width, $size->width()) ); } if ($height = $this->getTargetHeight()) { $resized->setHeight( min($height, $size->height()) ); } return $resized; } /** * Resize given size to target size proportinally */ public function scale(SizeInterface $size): SizeInterface { $resized = new Rectangle($size->width(), $size->height()); if ($this->hasTargetWidth() && $this->hasTargetHeight()) { $resized->setWidth(min( $this->getProportionalWidth($size), $this->getTargetWidth() )); $resized->setHeight(min( $this->getProportionalHeight($size), $this->getTargetHeight() )); } elseif ($this->hasTargetWidth()) { $resized->setWidth($this->getTargetWidth()); $resized->setHeight($this->getProportionalHeight($size)); } elseif ($this->hasTargetHeight()) { $resized->setWidth($this->getProportionalWidth($size)); $resized->setHeight($this->getTargetHeight()); } return $resized; } /** * Resize given size to target size proportinally but do not exceed original size */ public function scaleDown(SizeInterface $size): SizeInterface { $resized = new Rectangle($size->width(), $size->height()); if ($this->hasTargetWidth() && $this->hasTargetHeight()) { $resized->setWidth(min( $this->getProportionalWidth($size), $this->getTargetWidth(), $size->width() )); $resized->setHeight(min( $this->getProportionalHeight($size), $this->getTargetHeight(), $size->height() )); } elseif ($this->hasTargetWidth()) { $resized->setWidth(min( $this->getTargetWidth(), $size->width() )); $resized->setHeight(min( $this->getProportionalHeight($size), $size->height() )); } elseif ($this->hasTargetHeight()) { $resized->setWidth(min( $this->getProportionalWidth($size), $size->width() )); $resized->setHeight(min( $this->getTargetHeight(), $size->height() )); } return $resized; } /** * Scale given size to cover target size * * @param SizeInterface $size Size to be resized * @throws GeometryException */ public function cover(SizeInterface $size): SizeInterface { $resized = new Rectangle($size->width(), $size->height()); // auto height $resized->setWidth($this->getTargetWidth()); $resized->setHeight($this->getProportionalHeight($size)); if ($resized->fitsInto($this->getTargetSize())) { // auto width $resized->setWidth($this->getProportionalWidth($size)); $resized->setHeight($this->getTargetHeight()); } return $resized; } /** * Scale given size to contain target size * * @param SizeInterface $size Size to be resized * @throws GeometryException */ public function contain(SizeInterface $size): SizeInterface { $resized = new Rectangle($size->width(), $size->height()); // auto height $resized->setWidth($this->getTargetWidth()); $resized->setHeight($this->getProportionalHeight($size)); if (!$resized->fitsInto($this->getTargetSize())) { // auto width $resized->setWidth($this->getProportionalWidth($size)); $resized->setHeight($this->getTargetHeight()); } return $resized; } /** * Scale given size to contain target size but prevent upsizing * * @param SizeInterface $size Size to be resized * @throws GeometryException */ public function containDown(SizeInterface $size): SizeInterface { $resized = new Rectangle($size->width(), $size->height()); // auto height $resized->setWidth( min($size->width(), $this->getTargetWidth()) ); $resized->setHeight( min($size->height(), $this->getProportionalHeight($size)) ); if (!$resized->fitsInto($this->getTargetSize())) { // auto width $resized->setWidth( min($size->width(), $this->getProportionalWidth($size)) ); $resized->setHeight( min($size->height(), $this->getTargetHeight()) ); } return $resized; } /** * Crop target size out of given size at given position (i.e. move the pivot point) */ public function crop(SizeInterface $size, string $position = 'top-left'): SizeInterface { return $this->resize($size)->alignPivotTo( $size->movePivot($position), $position ); } } ================================================ FILE: src/Geometry/Traits/HasBackgroundColor.php ================================================ backgroundColor = $color; return $this; } /** * {@inheritdoc} * * @see DrawableInterface::backgroundColor() */ public function backgroundColor(): mixed { return $this->backgroundColor; } /** * {@inheritdoc} * * @see DrawableInterface::hasBackgroundColor() */ public function hasBackgroundColor(): bool { return !empty($this->backgroundColor); } } ================================================ FILE: src/Geometry/Traits/HasBorder.php ================================================ setBorderSize($size)->setBorderColor($color); } /** * {@inheritdoc} * * @see DrawableInterface::setBorderSize() */ public function setBorderSize(int $size): self { $this->borderSize = $size; return $this; } /** * {@inheritdoc} * * @see DrawableInterface::borderSize() */ public function borderSize(): int { return $this->borderSize; } /** * {@inheritdoc} * * @see DrawableInterface::setBorderColor() */ public function setBorderColor(mixed $color): self { $this->borderColor = $color; return $this; } /** * {@inheritdoc} * * @see DrawableInterface::borderColor() */ public function borderColor(): mixed { return $this->borderColor; } /** * {@inheritdoc} * * @see DrawableInterface::hasBorder() */ public function hasBorder(): bool { return $this->borderSize > 0 && !is_null($this->borderColor); } } ================================================ FILE: src/Image.php ================================================ origin = new Origin(); } /** * {@inheritdoc} * * @see ImageInterface::driver() */ public function driver(): DriverInterface { return $this->driver; } /** * {@inheritdoc} * * @see ImageInterface::core() */ public function core(): CoreInterface { return $this->core; } /** * {@inheritdoc} * * @see ImageInterface::origin() */ public function origin(): Origin { return $this->origin; } /** * {@inheritdoc} * * @see ImageInterface::setOrigin() */ public function setOrigin(Origin $origin): ImageInterface { $this->origin = $origin; return $this; } /** * {@inheritdoc} * * @see ImageInterface::count() */ public function count(): int { return $this->core->count(); } /** * Implementation of IteratorAggregate * * @return Traversable */ public function getIterator(): Traversable { return $this->core; } /** * {@inheritdoc} * * @see ImageInterface::isAnimated() */ public function isAnimated(): bool { return $this->count() > 1; } /** * {@inheritdoc} * * @see ImageInterface::removeAnimation( */ public function removeAnimation(int|string $position = 0): ImageInterface { return $this->modify(new RemoveAnimationModifier($position)); } /** * {@inheritdoc} * * @see ImageInterface::sliceAnimation() */ public function sliceAnimation(int $offset = 0, ?int $length = null): ImageInterface { return $this->modify(new SliceAnimationModifier($offset, $length)); } /** * {@inheritdoc} * * @see ImageInterface::loops() */ public function loops(): int { return $this->core->loops(); } /** * {@inheritdoc} * * @see ImageInterface::setLoops() */ public function setLoops(int $loops): ImageInterface { $this->core->setLoops($loops); return $this; } /** * {@inheritdoc} * * @see ImageInterface::exif() */ public function exif(?string $query = null): mixed { return is_null($query) ? $this->exif : $this->exif->get($query); } /** * {@inheritdoc} * * @see ImageInterface::setExif() */ public function setExif(CollectionInterface $exif): ImageInterface { $this->exif = $exif; return $this; } /** * {@inheritdoc} * * @see ImageInterface::modify() */ public function modify(ModifierInterface $modifier): ImageInterface { return $this->driver->specialize($modifier)->apply($this); } /** * {@inheritdoc} * * @see ImageInterface::analyze() */ public function analyze(AnalyzerInterface $analyzer): mixed { return $this->driver->specialize($analyzer)->analyze($this); } /** * {@inheritdoc} * * @see ImageInterface::encode() */ public function encode(EncoderInterface $encoder = new AutoEncoder()): EncodedImageInterface { return $this->driver->specialize($encoder)->encode($this); } /** * {@inheritdoc} * * @see ImageInterface::save() */ public function save(?string $path = null, mixed ...$options): ImageInterface { $path = is_null($path) ? $this->origin()->filePath() : $path; if (is_null($path)) { throw new EncoderException('Could not determine file path to save.'); } try { // try to determine encoding format by file extension of the path $encoded = $this->encodeByPath($path, ...$options); } catch (EncoderException) { // fallback to encoding format by media type $encoded = $this->encodeByMediaType(null, ...$options); } $encoded->save($path); return $this; } /** * {@inheritdoc} * * @see ImageInterface::width() */ public function width(): int { return $this->analyze(new WidthAnalyzer()); } /** * {@inheritdoc} * * @see ImageInterface::height() */ public function height(): int { return $this->analyze(new HeightAnalyzer()); } /** * {@inheritdoc} * * @see ImageInterface::size() */ public function size(): SizeInterface { return new Rectangle($this->width(), $this->height()); } /** * {@inheritdoc} * * @see ImageInterface::colorspace() */ public function colorspace(): ColorspaceInterface { return $this->analyze(new ColorspaceAnalyzer()); } /** * {@inheritdoc} * * @see ImageInterface::setColorspace() */ public function setColorspace(string|ColorspaceInterface $colorspace): ImageInterface { return $this->modify(new ColorspaceModifier($colorspace)); } /** * {@inheritdoc} * * @see ImageInterface::resolution() */ public function resolution(): ResolutionInterface { return $this->analyze(new ResolutionAnalyzer()); } /** * {@inheritdoc} * * @see ImageInterface::setResolution() */ public function setResolution(float $x, float $y): ImageInterface { return $this->modify(new ResolutionModifier($x, $y)); } /** * {@inheritdoc} * * @see ImageInterface::pickColor() */ public function pickColor(int $x, int $y, int $frame_key = 0): ColorInterface { return $this->analyze(new PixelColorAnalyzer($x, $y, $frame_key)); } /** * {@inheritdoc} * * @see ImageInterface::pickColors() */ public function pickColors(int $x, int $y): CollectionInterface { return $this->analyze(new PixelColorsAnalyzer($x, $y)); } /** * {@inheritdoc} * * @see ImageInterface::blendingColor() */ public function blendingColor(): ColorInterface { return $this->driver()->handleInput( $this->driver()->config()->blendingColor ); } /** * {@inheritdoc} * * @see ImageInterface::setBlendingColor() */ public function setBlendingColor(mixed $color): ImageInterface { $this->driver()->config()->setOptions( blendingColor: $this->driver()->handleInput($color) ); return $this; } /** * {@inheritdoc} * * @see ImageInterface::blendTransparency() */ public function blendTransparency(mixed $color = null): ImageInterface { return $this->modify(new BlendTransparencyModifier($color)); } /** * {@inheritdoc} * * @see ImageInterface::profile() */ public function profile(): ProfileInterface { return $this->analyze(new ProfileAnalyzer()); } /** * {@inheritdoc} * * @see ImageInterface::setProfile() */ public function setProfile(ProfileInterface $profile): ImageInterface { return $this->modify(new ProfileModifier($profile)); } /** * {@inheritdoc} * * @see ImageInterface::removeProfile() */ public function removeProfile(): ImageInterface { return $this->modify(new ProfileRemovalModifier()); } /** * {@inheritdoc} * * @see ImageInterface::reduceColors() */ public function reduceColors(int $limit, mixed $background = 'transparent'): ImageInterface { return $this->modify(new QuantizeColorsModifier($limit, $background)); } /** * {@inheritdoc} * * @see ImageInterface::sharpen() */ public function sharpen(int $amount = 10): ImageInterface { return $this->modify(new SharpenModifier($amount)); } /** * {@inheritdoc} * * @see ImageInterface::invert() */ public function invert(): ImageInterface { return $this->modify(new InvertModifier()); } /** * {@inheritdoc} * * @see ImageInterface::pixelate() */ public function pixelate(int $size): ImageInterface { return $this->modify(new PixelateModifier($size)); } /** * {@inheritdoc} * * @see ImageInterface::greyscale() */ public function greyscale(): ImageInterface { return $this->modify(new GreyscaleModifier()); } /** * {@inheritdoc} * * @see ImageInterface::brightness() */ public function brightness(int $level): ImageInterface { return $this->modify(new BrightnessModifier($level)); } /** * {@inheritdoc} * * @see ImageInterface::contrast() */ public function contrast(int $level): ImageInterface { return $this->modify(new ContrastModifier($level)); } /** * {@inheritdoc} * * @see ImageInterface::gamma() */ public function gamma(float $gamma): ImageInterface { return $this->modify(new GammaModifier($gamma)); } /** * {@inheritdoc} * * @see ImageInterface::colorize() */ public function colorize(int $red = 0, int $green = 0, int $blue = 0): ImageInterface { return $this->modify(new ColorizeModifier($red, $green, $blue)); } /** * {@inheritdoc} * * @see ImageInterface::flip() */ public function flip(): ImageInterface { return $this->modify(new FlipModifier()); } /** * {@inheritdoc} * * @see ImageInterface::flop() */ public function flop(): ImageInterface { return $this->modify(new FlopModifier()); } /** * {@inheritdoc} * * @see ImageInterface::blur() */ public function blur(int $amount = 5): ImageInterface { return $this->modify(new BlurModifier($amount)); } /** * {@inheritdoc} * * @see ImageInterface::rotate() */ public function rotate(float $angle, mixed $background = 'ffffff'): ImageInterface { return $this->modify(new RotateModifier($angle, $background)); } /** * {@inheritdoc} * * @see ImageInterface::orient() */ public function orient(): ImageInterface { return $this->modify(new AlignRotationModifier()); } /** * {@inheritdoc} * * @see ImageInterface::text() */ public function text(string $text, int $x, int $y, callable|Closure|FontInterface $font): ImageInterface { return $this->modify( new TextModifier( $text, new Point($x, $y), call_user_func(new FontFactory($font)), ), ); } /** * {@inheritdoc} * * @see ImageInterface::resize() */ public function resize(?int $width = null, ?int $height = null): ImageInterface { return $this->modify(new ResizeModifier($width, $height)); } /** * {@inheritdoc} * * @see ImageInterface::resizeDown() */ public function resizeDown(?int $width = null, ?int $height = null): ImageInterface { return $this->modify(new ResizeDownModifier($width, $height)); } /** * {@inheritdoc} * * @see ImageInterface::scale() */ public function scale(?int $width = null, ?int $height = null): ImageInterface { return $this->modify(new ScaleModifier($width, $height)); } /** * {@inheritdoc} * * @see ImageInterface::scaleDown() */ public function scaleDown(?int $width = null, ?int $height = null): ImageInterface { return $this->modify(new ScaleDownModifier($width, $height)); } /** * {@inheritdoc} * * @see ImageInterface::cover() */ public function cover(int $width, int $height, string $position = 'center'): ImageInterface { return $this->modify(new CoverModifier($width, $height, $position)); } /** * {@inheritdoc} * * @see ImageInterface::coverDown() */ public function coverDown(int $width, int $height, string $position = 'center'): ImageInterface { return $this->modify(new CoverDownModifier($width, $height, $position)); } /** * {@inheritdoc} * * @see ImageInterface::resizeCanvas() */ public function resizeCanvas( ?int $width = null, ?int $height = null, mixed $background = 'ffffff', string $position = 'center' ): ImageInterface { return $this->modify(new ResizeCanvasModifier($width, $height, $background, $position)); } /** * {@inheritdoc} * * @see ImageInterface::resizeCanvasRelative() */ public function resizeCanvasRelative( ?int $width = null, ?int $height = null, mixed $background = 'ffffff', string $position = 'center' ): ImageInterface { return $this->modify(new ResizeCanvasRelativeModifier($width, $height, $background, $position)); } /** * {@inheritdoc} * * @see ImageInterface::padDown() */ public function pad( int $width, int $height, mixed $background = 'ffffff', string $position = 'center' ): ImageInterface { return $this->modify(new PadModifier($width, $height, $background, $position)); } /** * {@inheritdoc} * * @see ImageInterface::pad() */ public function contain( int $width, int $height, mixed $background = 'ffffff', string $position = 'center' ): ImageInterface { return $this->modify(new ContainModifier($width, $height, $background, $position)); } /** * {@inheritdoc} * * @see ImageInterface::crop() */ public function crop( int $width, int $height, int $offset_x = 0, int $offset_y = 0, mixed $background = 'ffffff', string $position = 'top-left' ): ImageInterface { return $this->modify(new CropModifier($width, $height, $offset_x, $offset_y, $background, $position)); } /** * {@inheritdoc} * * @see ImageInterface::trim() */ public function trim(int $tolerance = 0): ImageInterface { return $this->modify(new TrimModifier($tolerance)); } /** * {@inheritdoc} * * @see ImageInterface::place() */ public function place( mixed $element, string $position = 'top-left', int $offset_x = 0, int $offset_y = 0, int $opacity = 100 ): ImageInterface { return $this->modify(new PlaceModifier($element, $position, $offset_x, $offset_y, $opacity)); } /** * {@inheritdoc} * * @see ImageInterface::fill() */ public function fill(mixed $color, ?int $x = null, ?int $y = null): ImageInterface { return $this->modify( new FillModifier( $color, is_null($x) || is_null($y) ? null : new Point($x, $y), ), ); } /** * {@inheritdoc} * * @see ImageInterface::drawPixel() */ public function drawPixel(int $x, int $y, mixed $color): ImageInterface { return $this->modify(new DrawPixelModifier(new Point($x, $y), $color)); } /** * {@inheritdoc} * * @see ImageInterface::drawRectangle() */ public function drawRectangle(int $x, int $y, callable|Closure|Rectangle $init): ImageInterface { return $this->modify( new DrawRectangleModifier( call_user_func(new RectangleFactory(new Point($x, $y), $init)), ), ); } /** * {@inheritdoc} * * @see ImageInterface::drawEllipse() */ public function drawEllipse(int $x, int $y, callable|Closure|Ellipse $init): ImageInterface { return $this->modify( new DrawEllipseModifier( call_user_func(new EllipseFactory(new Point($x, $y), $init)), ), ); } /** * {@inheritdoc} * * @see ImageInterface::drawCircle() */ public function drawCircle(int $x, int $y, callable|Closure|Circle $init): ImageInterface { return $this->modify( new DrawEllipseModifier( call_user_func(new CircleFactory(new Point($x, $y), $init)), ), ); } /** * {@inheritdoc} * * @see ImageInterface::drawPolygon() */ public function drawPolygon(callable|Closure|Polygon $init): ImageInterface { return $this->modify( new DrawPolygonModifier( call_user_func(new PolygonFactory($init)), ), ); } /** * {@inheritdoc} * * @see ImageInterface::drawLine() */ public function drawLine(callable|Closure|Line $init): ImageInterface { return $this->modify( new DrawLineModifier( call_user_func(new LineFactory($init)), ), ); } /** * {@inheritdoc} * * @see ImageInterface::drawBezier() */ public function drawBezier(callable|Closure|Bezier $init): ImageInterface { return $this->modify( new DrawBezierModifier( call_user_func(new BezierFactory($init)), ), ); } /** * {@inheritdoc} * * @see ImageInterface::encodeByMediaType() */ public function encodeByMediaType(null|string|MediaType $type = null, mixed ...$options): EncodedImageInterface { return $this->encode(new MediaTypeEncoder($type, ...$options)); } /** * {@inheritdoc} * * @see ImageInterface::encodeByExtension() */ public function encodeByExtension( null|string|FileExtension $extension = null, mixed ...$options ): EncodedImageInterface { return $this->encode(new FileExtensionEncoder($extension, ...$options)); } /** * {@inheritdoc} * * @see ImageInterface::encodeByPath() */ public function encodeByPath(?string $path = null, mixed ...$options): EncodedImageInterface { return $this->encode(new FilePathEncoder($path, ...$options)); } /** * {@inheritdoc} * * @see ImageInterface::toJpeg() */ public function toJpeg(mixed ...$options): EncodedImageInterface { return $this->encode(new JpegEncoder(...$options)); } /** * Alias of self::toJpeg() * * @throws RuntimeException */ public function toJpg(mixed ...$options): EncodedImageInterface { return $this->toJpeg(...$options); } /** * {@inheritdoc} * * @see ImageInterface::toJpeg() */ public function toJpeg2000(mixed ...$options): EncodedImageInterface { return $this->encode(new Jpeg2000Encoder(...$options)); } /** * ALias of self::toJpeg2000() * * @throws RuntimeException */ public function toJp2(mixed ...$options): EncodedImageInterface { return $this->toJpeg2000(...$options); } /** * {@inheritdoc} * * @see ImageInterface::toPng() */ public function toPng(mixed ...$options): EncodedImageInterface { return $this->encode(new PngEncoder(...$options)); } /** * {@inheritdoc} * * @see ImageInterface::toGif() */ public function toGif(mixed ...$options): EncodedImageInterface { return $this->encode(new GifEncoder(...$options)); } /** * {@inheritdoc} * * @see ImageInterface::toWebp() */ public function toWebp(mixed ...$options): EncodedImageInterface { return $this->encode(new WebpEncoder(...$options)); } /** * {@inheritdoc} * * @see ImageInterface::toBitmap() */ public function toBitmap(mixed ...$options): EncodedImageInterface { return $this->encode(new BmpEncoder(...$options)); } /** * Alias if self::toBitmap() * * @throws RuntimeException */ public function toBmp(mixed ...$options): EncodedImageInterface { return $this->toBitmap(...$options); } /** * {@inheritdoc} * * @see ImageInterface::toAvif() */ public function toAvif(mixed ...$options): EncodedImageInterface { return $this->encode(new AvifEncoder(...$options)); } /** * {@inheritdoc} * * @see ImageInterface::toTiff() */ public function toTiff(mixed ...$options): EncodedImageInterface { return $this->encode(new TiffEncoder(...$options)); } /** * Alias of self::toTiff() * * @throws RuntimeException */ public function toTif(mixed ...$options): EncodedImageInterface { return $this->toTiff(...$options); } /** * {@inheritdoc} * * @see ImageInterface::toHeic() */ public function toHeic(mixed ...$options): EncodedImageInterface { return $this->encode(new HeicEncoder(...$options)); } /** * Show debug info for the current image * * @return array */ public function __debugInfo(): array { try { return [ 'width' => $this->width(), 'height' => $this->height(), ]; } catch (RuntimeException) { return []; } } /** * Clone image */ public function __clone(): void { $this->driver = clone $this->driver; $this->core = clone $this->core; $this->exif = clone $this->exif; } } ================================================ FILE: src/ImageManager.php ================================================ driver = $this->resolveDriver($driver, ...$options); } /** * Create image manager with given driver * * @link https://image.intervention.io/v3/basics/configuration-drivers#static-constructor * * @throws DriverException * @throws InputException */ public static function withDriver(string|DriverInterface $driver, mixed ...$options): self { return new self(self::resolveDriver($driver, ...$options)); } /** * Create image manager with GD driver * * @link https://image.intervention.io/v3/basics/configuration-drivers#static-gd-driver-constructor * * @throws DriverException * @throws InputException */ public static function gd(mixed ...$options): self { return self::withDriver(new GdDriver(), ...$options); } /** * Create image manager with Imagick driver * * @link https://image.intervention.io/v3/basics/configuration-drivers#static-imagick-driver-constructor * * @throws DriverException * @throws InputException */ public static function imagick(mixed ...$options): self { return self::withDriver(new ImagickDriver(), ...$options); } /** * {@inheritdoc} * * @see ImageManagerInterface::create() */ public function create(int $width, int $height): ImageInterface { return $this->driver->createImage($width, $height); } /** * {@inheritdoc} * * @see ImageManagerInterface::read() */ public function read(mixed $input, string|array|DecoderInterface $decoders = []): ImageInterface { return $this->driver->handleInput( $input, match (true) { is_string($decoders), is_a($decoders, DecoderInterface::class) => [$decoders], default => $decoders, } ); } /** * {@inheritdoc} * * @see ImageManagerInterface::animate() */ public function animate(callable $init): ImageInterface { return $this->driver->createAnimation($init); } /** * {@inheritdoc} * * @see ImageManagerInterface::driver() */ public function driver(): DriverInterface { return $this->driver; } /** * Return driver object from given input which might be driver classname or instance of DriverInterface * * @throws DriverException * @throws InputException */ private static function resolveDriver(string|DriverInterface $driver, mixed ...$options): DriverInterface { $driver = match (true) { $driver instanceof DriverInterface => $driver, class_exists($driver) => new $driver(), default => throw new DriverException( 'Unable to resolve driver. Argment must be either an instance of ' . DriverInterface::class . '::class or a qualified namespaced name of the driver class.', ), }; if (!$driver instanceof DriverInterface) { throw new DriverException( 'Unable to resolve driver. Driver object must implement ' . DriverInterface::class . '.', ); } $driver->config()->setOptions(...$options); return $driver; } } ================================================ FILE: src/InputHandler.php ================================================ */ protected array $decoders = [ NativeObjectDecoder::class, ImageObjectDecoder::class, ColorObjectDecoder::class, RgbHexColorDecoder::class, RgbStringColorDecoder::class, CmykStringColorDecoder::class, HsvStringColorDecoder::class, HslStringColorDecoder::class, TransparentColorDecoder::class, HtmlColornameDecoder::class, FilePointerImageDecoder::class, FilePathImageDecoder::class, SplFileInfoImageDecoder::class, BinaryImageDecoder::class, DataUriImageDecoder::class, Base64ImageDecoder::class, EncodedImageObjectDecoder::class, ]; /** * Driver with which the decoder classes are specialized */ protected ?DriverInterface $driver = null; /** * Create new input handler instance with given decoder classnames * * @param array $decoders * @return void */ public function __construct(array $decoders = [], ?DriverInterface $driver = null) { $this->decoders = count($decoders) ? $decoders : $this->decoders; $this->driver = $driver; } /** * Static factory method * * @param array $decoders */ public static function withDecoders(array $decoders, ?DriverInterface $driver = null): self { return new self($decoders, $driver); } /** * {@inheritdoc} * * @see InputHandlerInterface::handle() */ public function handle(mixed $input): ImageInterface|ColorInterface { foreach ($this->decoders as $decoder) { try { // decode with driver specialized decoder return $this->resolve($decoder)->decode($input); } catch (DecoderException | NotSupportedException $e) { // try next decoder } } if (isset($e)) { throw new ($e::class)($e->getMessage()); } throw new DecoderException('Unable to decode input.'); } /** * Resolve the given classname to an decoder object * * @throws DriverException * @throws NotSupportedException */ private function resolve(string|DecoderInterface $decoder): DecoderInterface { if (($decoder instanceof DecoderInterface) && empty($this->driver)) { return $decoder; } if (($decoder instanceof DecoderInterface) && !empty($this->driver)) { return $this->driver->specialize($decoder); } if (empty($this->driver)) { return new $decoder(); } return $this->driver->specialize(new $decoder()); } } ================================================ FILE: src/Interfaces/AnalyzerInterface.php ================================================ */ interface CollectionInterface extends Traversable { /** * Determine if the collection has item at given key */ public function has(int|string $key): bool; /** * Add item to collection * * @return CollectionInterface */ public function push(mixed $item): self; /** * Return item for given key or return default is key does not exist */ public function get(int|string $key, mixed $default = null): mixed; /** * Return item at given numeric position starting at 0 */ public function getAtPosition(int $key = 0, mixed $default = null): mixed; /** * Return first item in collection */ public function first(): mixed; /** * Return last item in collection */ public function last(): mixed; /** * Return item count of collection */ public function count(): int; /** * Empty collection * * @return CollectionInterface */ public function empty(): self; /** * Transform collection as array * * @return array */ public function toArray(): array; /** * Extract items based on given values and discard the rest. * * @return CollectionInterface */ public function slice(int $offset, ?int $length = 0): self; } ================================================ FILE: src/Interfaces/ColorChannelInterface.php ================================================ */ public function toArray(): array; /** * Cast color object to hex encoded web color */ public function toHex(string $prefix = ''): string; /** * Return array of all color channels * * @return array */ public function channels(): array; /** * Return array of normalized color channel values * * @return array */ public function normalize(): array; /** * Retrieve the color channel by its classname * * @throws ColorException */ public function channel(string $classname): ColorChannelInterface; /** * Convert color to given colorspace */ public function convertTo(string|ColorspaceInterface $colorspace): self; /** * Determine if the current color is gray */ public function isGreyscale(): bool; /** * Determine if the current color is (semi) transparent */ public function isTransparent(): bool; /** * Determine whether the current color is completely transparent */ public function isClear(): bool; /** * Cast color object to string */ public function __toString(): string; } ================================================ FILE: src/Interfaces/ColorProcessorInterface.php ================================================ $normalized */ public function colorFromNormalized(array $normalized): ColorInterface; } ================================================ FILE: src/Interfaces/CoreInterface.php ================================================ */ public function setNative(mixed $native): self; /** * Count number of frames of animated image core */ public function count(): int; /** * Return frame of given position in an animated image * * @throws AnimationException */ public function frame(int $position): FrameInterface; /** * Add new frame to core * * @return CoreInterface */ public function add(FrameInterface $frame): self; /** * Return number of repetitions of an animated image */ public function loops(): int; /** * Set the number of repetitions for an animation. Where a * value of 0 means infinite repetition. * * @return CoreInterface */ public function setLoops(int $loops): self; /** * Get first frame in core * * @throws AnimationException */ public function first(): FrameInterface; /** * Get last frame in core * * @throws AnimationException */ public function last(): FrameInterface; } ================================================ FILE: src/Interfaces/DecoderInterface.php ================================================ $objects * @throws NotSupportedException * @throws DriverException * @return array */ public function specializeMultiple(array $objects): array; /** * Create new image instance with the current driver in given dimensions * * @throws RuntimeException */ public function createImage(int $width, int $height): ImageInterface; /** * Create new animated image * * @throws RuntimeException */ public function createAnimation(callable $init): ImageInterface; /** * Handle given input by decoding it to ImageInterface or ColorInterface * * @param array $decoders * @throws RuntimeException */ public function handleInput(mixed $input, array $decoders = []): ImageInterface|ColorInterface; /** * Return color processor for the given colorspace */ public function colorProcessor(ColorspaceInterface $colorspace): ColorProcessorInterface; /** * Return font processor of the current driver */ public function fontProcessor(): FontProcessorInterface; /** * Check whether all requirements for operating the driver are met and * throw exception if the check fails. * * @throws DriverException */ public function checkHealth(): void; /** * Check if the current driver supports the given format and if the * underlying PHP extension was built with support for the format. */ public function supports(string|Format|FileExtension|MediaType $identifier): bool; } ================================================ FILE: src/Interfaces/EncodedImageInterface.php ================================================ */ interface ImageInterface extends IteratorAggregate, Countable { /** * Return driver of current image */ public function driver(): DriverInterface; /** * Return core of current image */ public function core(): CoreInterface; /** * Return the origin of the image */ public function origin(): Origin; /** * Set the origin of the image */ public function setOrigin(Origin $origin): self; /** * Return width of current image * * @link https://image.intervention.io/v3/basics/meta-information#read-the-pixel-width * * @throws RuntimeException */ public function width(): int; /** * Return height of current image * * @link https://image.intervention.io/v3/basics/meta-information#read-the-pixel-height * * @throws RuntimeException */ public function height(): int; /** * Return size of current image * * @link https://image.intervention.io/v3/basics/meta-information#read-the-image-size-as-an-object * * @throws RuntimeException */ public function size(): SizeInterface; /** * Encode image with given encoder * * @link https://image.intervention.io/v3/basics/image-output#encode-images * * @throws RuntimeException */ public function encode(EncoderInterface $encoder = new AutoEncoder()): EncodedImageInterface; /** * Save the image to the specified path in the file system. If no path is * given, the image will be saved at its original location. * * @link https://image.intervention.io/v3/basics/image-output#encode--save-combined * * @throws RuntimeException */ public function save(?string $path = null, mixed ...$options): self; /** * Apply given modifier to current image * * @link https://image.intervention.io/v3/modifying-images/custom-modifiers * * @throws RuntimeException */ public function modify(ModifierInterface $modifier): self; /** * Analyzer current image with given analyzer * * @throws RuntimeException */ public function analyze(AnalyzerInterface $analyzer): mixed; /** * Determine if current image is animated * * @link https://image.intervention.io/v3/modifying-images/animations#check-the-current-image-instance-for-animation */ public function isAnimated(): bool; /** * Remove all frames but keep the one at the specified position * * It is possible to specify the position as integer or string values. * With the former, the exact position passed is searched for, while * string values must represent a percentage value between '0%' and '100%' * and the respective frame position is only determined approximately. * * @link https://image.intervention.io/v3/modifying-images/animations#remove-animation * * @throws RuntimeException */ public function removeAnimation(int|string $position = 0): self; /** * Extract animation frames based on given values and discard the rest * * @link https://image.intervention.io/v3/modifying-images/animations#change-the-animation-iteration-count * * @throws RuntimeException */ public function sliceAnimation(int $offset = 0, ?int $length = null): self; /** * Return loop count of animated image * * @link https://image.intervention.io/v3/modifying-images/animations#read-the-animation-iteration-count */ public function loops(): int; /** * Set loop count of animated image * * @link https://image.intervention.io/v3/modifying-images/animations#change-the-animation-iteration-count */ public function setLoops(int $loops): self; /** * Return exif data of current image * * @link https://image.intervention.io/v3/basics/meta-information#exif-information */ public function exif(?string $query = null): mixed; /** * Set exif data for the image object */ public function setExif(CollectionInterface $exif): self; /** * Return image resolution/density * * @link https://image.intervention.io/v3/basics/meta-information#image-resolution * * @throws RuntimeException */ public function resolution(): ResolutionInterface; /** * Set image resolution * * @link https://image.intervention.io/v3/basics/meta-information#image-resolution * * @throws RuntimeException */ public function setResolution(float $x, float $y): self; /** * Get the colorspace of the image * * @link https://image.intervention.io/v3/basics/colors#read-the-image-colorspace * * @throws RuntimeException */ public function colorspace(): ColorspaceInterface; /** * Transform image to given colorspace * * @link https://image.intervention.io/v3/basics/colors#change-the-image-colorspace * * @throws RuntimeException */ public function setColorspace(string|ColorspaceInterface $colorspace): self; /** * Return color of pixel at given position on given frame position * * @link https://image.intervention.io/v3/basics/colors#color-information * * @throws RuntimeException */ public function pickColor(int $x, int $y, int $frame_key = 0): ColorInterface; /** * Return all colors of pixel at given position for all frames of image * * @link https://image.intervention.io/v3/basics/colors#color-information * * @throws RuntimeException */ public function pickColors(int $x, int $y): CollectionInterface; /** * Return color that is mixed with transparent areas when converting to a format which * does not support transparency. * * @throws RuntimeException */ public function blendingColor(): ColorInterface; /** * Set blending color will have no effect unless image is converted into a format * which does not support transparency. * * @throws RuntimeException */ public function setBlendingColor(mixed $color): self; /** * Replace transparent areas of the image with given color * * @throws RuntimeException */ public function blendTransparency(mixed $color = null): self; /** * Retrieve ICC color profile of image * * @link https://image.intervention.io/v3/basics/colors#color-profiles * * @throws RuntimeException */ public function profile(): ProfileInterface; /** * Set given icc color profile to image * * @link https://image.intervention.io/v3/basics/colors#color-profiles * * @throws RuntimeException */ public function setProfile(ProfileInterface $profile): self; /** * Remove ICC color profile from the current image * * @link https://image.intervention.io/v3/basics/colors#color-profiles * * @throws RuntimeException */ public function removeProfile(): self; /** * Apply color quantization to the current image * * @link https://image.intervention.io/v3/modifying-images/effects#reduce-colors * * @throws RuntimeException */ public function reduceColors(int $limit, mixed $background = 'transparent'): self; /** * Sharpen the current image with given strength * * @link https://image.intervention.io/v3/modifying-images/effects#sharpening-effect * * @throws RuntimeException */ public function sharpen(int $amount = 10): self; /** * Turn image into a greyscale version * * @link https://image.intervention.io/v3/modifying-images/effects#convert-image-to-a-greyscale-version * * @throws RuntimeException */ public function greyscale(): self; /** * Adjust brightness of the current image * * @link https://image.intervention.io/v3/modifying-images/effects#change-the-image-brightness * * @throws RuntimeException */ public function brightness(int $level): self; /** * Adjust color contrast of the current image * * @link https://image.intervention.io/v3/modifying-images/effects#change-the-image-contrast * * @throws RuntimeException */ public function contrast(int $level): self; /** * Apply gamma correction on the current image * * @link https://image.intervention.io/v3/modifying-images/effects#gamma-correction * * @throws RuntimeException */ public function gamma(float $gamma): self; /** * Adjust the intensity of the RGB color channels * * @link https://image.intervention.io/v3/modifying-images/effects#color-correction * * @throws RuntimeException */ public function colorize(int $red = 0, int $green = 0, int $blue = 0): self; /** * Mirror the current image vertically by swapping top and bottom * * @link https://image.intervention.io/v3/modifying-images/effects#mirror-image-vertically * * @throws RuntimeException */ public function flip(): self; /** * Mirror the current image horizontally by swapping left and right * * @link https://image.intervention.io/v3/modifying-images/effects#mirror-image-horizontally * * @throws RuntimeException */ public function flop(): self; /** * Blur current image by given strength * * @link https://image.intervention.io/v3/modifying-images/effects#blur-effect * * @throws RuntimeException */ public function blur(int $amount = 5): self; /** * Invert the colors of the current image * * @link https://image.intervention.io/v3/modifying-images/effects#invert-colors * * @throws RuntimeException */ public function invert(): self; /** * Apply pixelation filter effect on current image * * @link https://image.intervention.io/v3/modifying-images/effects#pixelation-effect * * @throws RuntimeException */ public function pixelate(int $size): self; /** * Rotate current image by given angle * * @link https://image.intervention.io/v3/modifying-images/effects#image-rotation * * @param string $background * @throws RuntimeException */ public function rotate(float $angle, mixed $background = 'ffffff'): self; /** * Rotate the image to be upright according to exif information * * @link https://image.intervention.io/v3/modifying-images/effects#image-orientation-according-to-exif-data * * @throws RuntimeException */ public function orient(): self; /** * Draw text on image * * @link https://image.intervention.io/v3/modifying-images/text-fonts * * @throws RuntimeException */ public function text(string $text, int $x, int $y, callable|Closure|FontInterface $font): self; /** * Resize image to the given width and/or height * * @link https://image.intervention.io/v3/modifying-images/resizing#simple-image-resizing * * @throws RuntimeException */ public function resize(?int $width = null, ?int $height = null): self; /** * Resize image to the given width and/or height without exceeding the original dimensions * * @link https://image.intervention.io/v3/modifying-images/resizing#resize-without-exceeding-the-original-size * * @throws RuntimeException */ public function resizeDown(?int $width = null, ?int $height = null): self; /** * Resize image to the given width and/or height and keep the original aspect ratio * * @link https://image.intervention.io/v3/modifying-images/resizing#resize-images-proportionally * * @throws RuntimeException */ public function scale(?int $width = null, ?int $height = null): self; /** * Resize image to the given width and/or height, keep the original aspect ratio * and do not exceed the original image width or height * * @link https://image.intervention.io/v3/modifying-images/resizing#scale-images-but-do-not-exceed-the-original-size * * @throws RuntimeException */ public function scaleDown(?int $width = null, ?int $height = null): self; /** * Takes the specified width and height and scales them to the largest * possible size that fits within the original size. This scaled size is * then positioned on the original and cropped, before this result is resized * to the desired size using the arguments * * @link https://image.intervention.io/v3/modifying-images/resizing#cropping--resizing-combined * * @throws RuntimeException */ public function cover(int $width, int $height, string $position = 'center'): self; /** * Same as cover() but do not exceed the original image size * * @link https://image.intervention.io/v3/modifying-images/resizing#fitted-resizing-without-exceeding-the-original-size * * @throws RuntimeException */ public function coverDown(int $width, int $height, string $position = 'center'): self; /** * Resize the boundaries of the current image to given width and height. * An anchor position can be defined to determine where the original image * is fixed. A background color can be passed to define the color of the * new emerging areas. * * @link https://image.intervention.io/v3/modifying-images/resizing#resize-image-boundaries-without-resampling-the-original-image * * @throws RuntimeException */ public function resizeCanvas( ?int $width = null, ?int $height = null, mixed $background = 'ffffff', string $position = 'center' ): self; /** * Resize canvas in the same way as resizeCanvas() but takes relative values * for the width and height, which will be added or subtracted to the * original image size. * * @link https://image.intervention.io/v3/modifying-images/resizing#resize-image-boundaries-relative-to-the-original * * @throws RuntimeException */ public function resizeCanvasRelative( ?int $width = null, ?int $height = null, mixed $background = 'ffffff', string $position = 'center' ): self; /** * Padded resizing means that the original image is scaled until it fits the * defined target size with unchanged aspect ratio. The original image is * not scaled up but only down. * * Compared to the cover() method, this method does not create cropped areas, * but possibly new empty areas on the sides of the result image. These are * filled with the specified background color. * * @link https://image.intervention.io/v3/modifying-images/resizing#resizing--padding-combined * * @param string $background * @throws RuntimeException */ public function pad( int $width, int $height, mixed $background = 'ffffff', string $position = 'center' ): self; /** * This method does the same as pad(), but the original image is also scaled * up if the target size exceeds the original size. * * @link https://image.intervention.io/v3/modifying-images/resizing#padded-resizing-with-upscaling * * @param string $background * @throws RuntimeException */ public function contain( int $width, int $height, mixed $background = 'ffffff', string $position = 'center' ): self; /** * Cut out a rectangular part of the current image with given width and * height at a given position. Define optional x,y offset coordinates * to move the cutout by the given amount of pixels. * * @link https://image.intervention.io/v3/modifying-images/resizing#cut-out-a-rectangular-part * * @throws RuntimeException */ public function crop( int $width, int $height, int $offset_x = 0, int $offset_y = 0, mixed $background = 'ffffff', string $position = 'top-left' ): self; /** * Trim the image by removing border areas of similar color within a the given tolerance * * @link https://image.intervention.io/v3/modifying-images/resizing#remove-border-areas-in-similar-color * * @throws RuntimeException * @throws AnimationException */ public function trim(int $tolerance = 0): self; /** * Place another image into the current image instance * * @link https://image.intervention.io/v3/modifying-images/inserting#insert-images * * @throws RuntimeException */ public function place( mixed $element, string $position = 'top-left', int $offset_x = 0, int $offset_y = 0, int $opacity = 100 ): self; /** * Fill image with given color * * If an optional position is specified for the filling process ln the form * of x and y coordinates, the process is executed as flood fill. This means * that the color at the specified position is taken as a reference and all * adjacent pixels are also filled with the filling color. * * If no coordinates are specified, the entire image area is filled. * * @link https://image.intervention.io/v3/modifying-images/drawing#fill-images-with-color * * @throws RuntimeException */ public function fill(mixed $color, ?int $x = null, ?int $y = null): self; /** * Draw a single pixel at given position defined by the coordinates x and y in a given color. * * @link https://image.intervention.io/v3/modifying-images/drawing#draw-pixels * * @throws RuntimeException */ public function drawPixel(int $x, int $y, mixed $color): self; /** * Draw a rectangle on the current image * * @link https://image.intervention.io/v3/modifying-images/drawing#draw-a-rectangle * * @throws RuntimeException */ public function drawRectangle(int $x, int $y, callable|Closure|Rectangle $init): self; /** * Draw ellipse on the current image * * @link https://image.intervention.io/v3/modifying-images/drawing#draw-ellipses * * @throws RuntimeException */ public function drawEllipse(int $x, int $y, callable|Closure|Ellipse $init): self; /** * Draw circle on the current image * * @link https://image.intervention.io/v3/modifying-images/drawing#draw-a-circle * * @throws RuntimeException */ public function drawCircle(int $x, int $y, callable|Closure|Circle $init): self; /** * Draw a polygon on the current image * * @link https://image.intervention.io/v3/modifying-images/drawing#draw-a-polygon * * @throws RuntimeException */ public function drawPolygon(callable|Closure|Polygon $init): self; /** * Draw a line on the current image * * @link https://image.intervention.io/v3/modifying-images/drawing#draw-a-line * * @throws RuntimeException */ public function drawLine(callable|Closure|Line $init): self; /** * Draw a bezier curve on the current image * * @link https://image.intervention.io/v3/modifying-images/drawing#draw-bezier-curves * * @throws RuntimeException */ public function drawBezier(callable|Closure|Bezier $init): self; /** * Encode image to given media (mime) type. If no type is given the image * will be encoded to the format of the originally read image. * * @link https://image.intervention.io/v3/basics/image-output#encode-images-by-media-mime-type * * @throws RuntimeException */ public function encodeByMediaType(null|string|MediaType $type = null, mixed ...$options): EncodedImageInterface; /** * Encode the image into the format represented by the given extension. If no * extension is given the image will be encoded to the format of the * originally read image. * * @link https://image.intervention.io/v3/basics/image-output#encode-images-by-file-extension * * @throws RuntimeException */ public function encodeByExtension( null|string|FileExtension $extension = null, mixed ...$options ): EncodedImageInterface; /** * Encode the image into the format represented by the given extension of * the given file path extension is given the image will be encoded to * the format of the originally read image. * * @link https://image.intervention.io/v3/basics/image-output#encode-images-by-file-path * * @throws RuntimeException */ public function encodeByPath(?string $path = null, mixed ...$options): EncodedImageInterface; /** * Encode image to JPEG format * * @link https://image.intervention.io/v3/basics/image-output#encode-jpeg-format * * @throws RuntimeException */ public function toJpeg(mixed ...$options): EncodedImageInterface; /** * Encode image to Jpeg2000 format * * @link https://image.intervention.io/v3/basics/image-output#encode-jpeg-2000-format * * @throws RuntimeException */ public function toJpeg2000(mixed ...$options): EncodedImageInterface; /** * Encode image to Webp format * * @link https://image.intervention.io/v3/basics/image-output#encode-webp-format * * @throws RuntimeException */ public function toWebp(mixed ...$options): EncodedImageInterface; /** * Encode image to PNG format * * @link https://image.intervention.io/v3/basics/image-output#encode-png-format * * @throws RuntimeException */ public function toPng(mixed ...$options): EncodedImageInterface; /** * Encode image to GIF format * * @link https://image.intervention.io/v3/basics/image-output#encode-gif-format * * @throws RuntimeException */ public function toGif(mixed ...$options): EncodedImageInterface; /** * Encode image to Bitmap format * * @link https://image.intervention.io/v3/basics/image-output#encode-windows-bitmap-format * * @throws RuntimeException */ public function toBitmap(mixed ...$options): EncodedImageInterface; /** * Encode image to AVIF format * * @link https://image.intervention.io/v3/basics/image-output#encode-av1-image-file-format-avif * * @throws RuntimeException */ public function toAvif(mixed ...$options): EncodedImageInterface; /** * Encode image to TIFF format * * @link https://image.intervention.io/v3/basics/image-output#encode-tiff-format * * @throws RuntimeException */ public function toTiff(mixed ...$options): EncodedImageInterface; /** * Encode image to HEIC format * * @link https://image.intervention.io/v3/basics/image-output#encode-heic-format * * @throws RuntimeException */ public function toHeic(mixed ...$options): EncodedImageInterface; } ================================================ FILE: src/Interfaces/ImageManagerInterface.php ================================================ |DecoderInterface $decoders * @throws RuntimeException */ public function read(mixed $input, string|array|DecoderInterface $decoders = []): ImageInterface; /** * Create new animated image by given callback * * @link https://image.intervention.io/v3/basics/instantiation#create-animations * * @throws RuntimeException */ public function animate(callable $init): ImageInterface; /** * Return currently used driver */ public function driver(): DriverInterface; } ================================================ FILE: src/Interfaces/InputHandlerInterface.php ================================================ */ public function specializable(): array; /** * Set the driver for which the object is specialized * * @throws DriverException */ public function setDriver(DriverInterface $driver): self; /** * Return the driver for which the object was specialized */ public function driver(): DriverInterface; } ================================================ FILE: src/Interfaces/SpecializedInterface.php ================================================ mediaType(); } if ($identifier instanceof FileExtension) { return $identifier->mediaType(); } try { $type = self::from(strtolower($identifier)); } catch (Error) { try { $type = FileExtension::from(strtolower($identifier))->mediaType(); } catch (Error) { throw new NotSupportedException('Unable to create media type from "' . $identifier . '".'); } } return $type; } /** * Try to create media type from given identifier and return null on failure * * @param string|Format|MediaType|FileExtension $identifier * @return MediaType|null */ public static function tryCreate(string|self|Format|FileExtension $identifier): ?self { try { return self::create($identifier); } catch (NotSupportedException) { return null; } } /** * Return the matching format for the current media (MIME) type */ public function format(): Format { return match ($this) { self::IMAGE_JPEG, self::IMAGE_JPG, self::IMAGE_PJPEG, self::IMAGE_X_JPEG => Format::JPEG, self::IMAGE_WEBP, self::IMAGE_X_WEBP => Format::WEBP, self::IMAGE_GIF => Format::GIF, self::IMAGE_PNG, self::IMAGE_X_PNG => Format::PNG, self::IMAGE_AVIF, self::IMAGE_X_AVIF => Format::AVIF, self::IMAGE_BMP, self::IMAGE_MS_BMP, self::IMAGE_X_BITMAP, self::IMAGE_X_BMP, self::IMAGE_X_MS_BMP, self::IMAGE_X_XBITMAP, self::IMAGE_X_WINDOWS_BMP, self::IMAGE_X_BMP3, self::IMAGE_X_WIN_BITMAP => Format::BMP, self::IMAGE_TIFF => Format::TIFF, self::IMAGE_JP2, self::IMAGE_JPX, self::IMAGE_X_JP2_CODESTREAM, self::IMAGE_JPM => Format::JP2, self::IMAGE_HEIF, self::IMAGE_HEIC, self::IMAGE_X_HEIC => Format::HEIC, }; } /** * Return the possible file extension for the current media type * * @return array */ public function fileExtensions(): array { return $this->format()->fileExtensions(); } /** * Return the first file extension for the current media type */ public function fileExtension(): FileExtension { return $this->format()->fileExtension(); } } ================================================ FILE: src/ModifierStack.php ================================================ $modifiers * @return void */ public function __construct(protected array $modifiers) { // } /** * Apply all modifiers in stack to the given image */ public function apply(ImageInterface $image): ImageInterface { foreach ($this->modifiers as $modifier) { $modifier->apply($image); } return $image; } /** * Append new modifier to the stack */ public function push(ModifierInterface $modifier): self { $this->modifiers[] = $modifier; return $this; } } ================================================ FILE: src/Modifiers/AbstractDrawModifier.php ================================================ driver()->handleInput($this->drawable()->backgroundColor()); } catch (DecoderException) { return $this->driver()->handleInput('transparent'); } return $color; } /** * @throws RuntimeException */ public function borderColor(): ColorInterface { try { $color = $this->driver()->handleInput($this->drawable()->borderColor()); } catch (DecoderException) { return $this->driver()->handleInput('transparent'); } return $color; } } ================================================ FILE: src/Modifiers/AlignRotationModifier.php ================================================ handleInput( $this->color ?: $driver->config()->blendingColor ); // replace alpha channel value with opaque value if ($color->isTransparent()) { return new Color( $color->channel(Red::class)->value(), $color->channel(Green::class)->value(), $color->channel(Blue::class)->value(), ); } return $color; } } ================================================ FILE: src/Modifiers/BlurModifier.php ================================================ target)) { return $this->target; } if (in_array($this->target, ['rgb', 'RGB', RgbColorspace::class])) { return new RgbColorspace(); } if (in_array($this->target, ['cmyk', 'CMYK', CmykColorspace::class])) { return new CmykColorspace(); } throw new NotSupportedException('Given colorspace is not supported.'); } } ================================================ FILE: src/Modifiers/ContainModifier.php ================================================ size() ->contain( $this->width, $this->height ) ->alignPivotTo( $this->getResizeSize($image), $this->position ); } public function getResizeSize(ImageInterface $image): SizeInterface { return new Rectangle($this->width, $this->height); } } ================================================ FILE: src/Modifiers/ContrastModifier.php ================================================ size(); $crop = new Rectangle($this->width, $this->height); return $crop->contain( $imagesize->width(), $imagesize->height() )->alignPivotTo($imagesize, $this->position); } /** * @throws RuntimeException */ public function getResizeSize(SizeInterface $size): SizeInterface { return $size->resize($this->width, $this->height); } } ================================================ FILE: src/Modifiers/CropModifier.php ================================================ width, $this->height); $crop->align($this->position); return $crop->alignPivotTo( $image->size(), $this->position ); } } ================================================ FILE: src/Modifiers/DrawBezierModifier.php ================================================ drawable; } } ================================================ FILE: src/Modifiers/DrawEllipseModifier.php ================================================ drawable; } } ================================================ FILE: src/Modifiers/DrawLineModifier.php ================================================ drawable; } } ================================================ FILE: src/Modifiers/DrawPixelModifier.php ================================================ drawable; } } ================================================ FILE: src/Modifiers/DrawRectangleModifier.php ================================================ drawable; } } ================================================ FILE: src/Modifiers/FillModifier.php ================================================ position instanceof PointInterface; } } ================================================ FILE: src/Modifiers/FlipModifier.php ================================================ size() ->containMax( $this->width, $this->height ) ->alignPivotTo( $this->getResizeSize($image), $this->position ); } } ================================================ FILE: src/Modifiers/PixelateModifier.php ================================================ size()->movePivot( $this->position, $this->offset_x, $this->offset_y ); $watermark_size = $watermark->size()->movePivot( $this->position ); return $image_size->relativePositionTo($watermark_size); } } ================================================ FILE: src/Modifiers/ProfileModifier.php ================================================ core()->frame($this->normalizePosition($image)); } /** * Return the position of the selected frame as integer * * @throws InputException */ protected function normalizePosition(ImageInterface $image): int { if (is_int($this->position)) { return $this->position; } if (is_numeric($this->position)) { return (int) $this->position; } // calculate position from percentage value if (preg_match("/^(?P[0-9]{1,3})%$/", $this->position, $matches) != 1) { throw new InputException( 'Position must be either integer or a percent value as string.' ); } $total = count($image); $position = intval(round($total / 100 * intval($matches['percent']))); return $position == $total ? $position - 1 : $position; } } ================================================ FILE: src/Modifiers/ResizeCanvasModifier.php ================================================ new Rectangle( is_null($this->width) ? $image->width() : $image->width() + $this->width, is_null($this->height) ? $image->height() : $image->height() + $this->height, ), default => new Rectangle( is_null($this->width) ? $image->width() : $this->width, is_null($this->height) ? $image->height() : $this->height, ), }; return $size->alignPivotTo($image->size(), $this->position); } } ================================================ FILE: src/Modifiers/ResizeCanvasRelativeModifier.php ================================================ angle, 360); } } ================================================ FILE: src/Modifiers/ScaleDownModifier.php ================================================ driver()->handleInput($this->font->color()); if ($this->font->hasStrokeEffect() && $color->isTransparent()) { throw new ColorException( 'The text color must be fully opaque when using the stroke effect.' ); } return $color; } /** * Decode outline stroke color * * @throws RuntimeException * @throws ColorException */ protected function strokeColor(): ColorInterface { $color = $this->driver()->handleInput($this->font->strokeColor()); if ($color->isTransparent()) { throw new ColorException( 'The stroke color must be fully opaque.' ); } return $color; } /** * Return array of offset points to draw text stroke effect below the actual text * * @return array */ protected function strokeOffsets(FontInterface $font): array { $offsets = []; if ($font->strokeWidth() <= 0) { return $offsets; } for ($x = $font->strokeWidth() * -1; $x <= $font->strokeWidth(); $x++) { for ($y = $font->strokeWidth() * -1; $y <= $font->strokeWidth(); $y++) { $offsets[] = new Point($x, $y); } } return $offsets; } } ================================================ FILE: src/Modifiers/TrimModifier.php ================================================ mediaType; } /** * Alias of self::mediaType() */ public function mimetype(): string { return $this->mediaType(); } /** * Set media type of current instance */ public function setMediaType(string|MediaType $type): self { $this->mediaType = match (true) { is_string($type) => $type, default => $type->value, }; return $this; } /** * Return file path of origin */ public function filePath(): ?string { return $this->filePath; } /** * Set file path for origin */ public function setFilePath(string $path): self { $this->filePath = $path; return $this; } /** * Return file extension if origin was created from file path */ public function fileExtension(): ?string { return pathinfo($this->filePath ?: '', PATHINFO_EXTENSION) ?: null; } /** * Show debug info for the current image * * @return array */ public function __debugInfo(): array { return [ 'mediaType' => $this->mediaType(), 'filePath' => $this->filePath(), ]; } } ================================================ FILE: src/Resolution.php ================================================ */ class Resolution implements ResolutionInterface, Stringable, IteratorAggregate { public const PER_INCH = 1; public const PER_CM = 2; /** * Create new instance */ public function __construct( protected float $x, protected float $y, protected int $per_unit = self::PER_INCH ) { // } /** * {@inheritdoc} * * @see IteratorAggregate::getIterator() */ public function getIterator(): Traversable { return new ArrayIterator([$this->x, $this->y]); } /** * {@inheritdoc} * * @see ResolutionInterface::x() */ public function x(): float { return $this->x; } /** * {@inheritdoc} * * @see ResolutionInterface::setX() */ public function setX(float $x): self { $this->x = $x; return $this; } /** * {@inheritdoc} * * @see ResolutionInterface::y() */ public function y(): float { return $this->y; } /** * {@inheritdoc} * * @see ResolutionInterface::setY() */ public function setY(float $y): self { $this->y = $y; return $this; } /** * {@inheritdoc} * * @see ResolutionInterface::setPerUnit() */ protected function setPerUnit(int $per_unit): self { $this->per_unit = $per_unit; return $this; } /** * {@inheritdoc} * * @see ResolutionInterface::unit() */ public function unit(): string { return match ($this->per_unit) { self::PER_CM => 'dpcm', default => 'dpi', }; } /** * {@inheritdoc} * * @see ResolutionInterface::perInch() */ public function perInch(): self { return match ($this->per_unit) { self::PER_CM => $this ->setPerUnit(self::PER_INCH) ->setX($this->x * 2.54) ->setY($this->y * 2.54), default => $this }; } /** * {@inheritdoc} * * @see ResolutionInterface::perCm() */ public function perCm(): self { return match ($this->per_unit) { self::PER_INCH => $this ->setPerUnit(self::PER_CM) ->setX($this->x / 2.54) ->setY($this->y / 2.54), default => $this, }; } /** * {@inheritdoc} * * @see ResolutionInterface::toString() */ public function toString(): string { return sprintf("%1\$.2f x %2\$.2f %3\$s", $this->x, $this->y, $this->unit()); } /** * {@inheritdoc} * * @see ResolutionInterface::__toString() */ public function __toString(): string { return $this->toString(); } } ================================================ FILE: src/Traits/CanBeDriverSpecialized.php ================================================ getConstructor()) { foreach ($constructor->getParameters() as $parameter) { $specializable[$parameter->getName()] = $this->{$parameter->getName()}; } } return $specializable; } /** * {@inheritdoc} * * @see SpecializableInterface::driver() */ public function driver(): DriverInterface { return $this->driver; } /** * {@inheritdoc} * * @see SpecializableInterface::setDriver() */ public function setDriver(DriverInterface $driver): SpecializableInterface { if (!$this->belongsToDriver($driver)) { throw new DriverException( "Class '" . $this::class . "' can not be used with " . $driver->id() . " driver." ); } $this->driver = $driver; return $this; } /** * Determine if the current object belongs to the given driver's namespace */ protected function belongsToDriver(object $driver): bool { $namespace = function (object $object): string { return (new ReflectionClass($object))->getNamespaceName(); }; return str_starts_with($namespace($this), $namespace($driver)); } } ================================================ FILE: src/Traits/CanBuildFilePointer.php ================================================ filename = $filename; } /** * {@inheritdoc} * * @see FontInterface::setSize() */ public function setSize(float $size): FontInterface { $this->size = $size; return $this; } /** * {@inheritdoc} * * @see FontInterface::size() */ public function size(): float { return $this->size; } /** * {@inheritdoc} * * @see FontInterface::setAngle() */ public function setAngle(float $angle): FontInterface { $this->angle = $angle; return $this; } /** * {@inheritdoc} * * @see FontInterface::angle() */ public function angle(): float { return $this->angle; } /** * {@inheritdoc} * * @see FontInterface::setFilename() * * @throws FontException */ public function setFilename(string $filename): FontInterface { if (!file_exists($filename)) { throw new FontException('Font file ' . $filename . ' does not exist.'); } $this->filename = $filename; return $this; } /** * {@inheritdoc} * * @see FontInterface::filename() */ public function filename(): ?string { return $this->filename; } /** * {@inheritdoc} * * @see FontInterface::hasFilename() */ public function hasFilename(): bool { return !is_null($this->filename) && is_file($this->filename); } /** * {@inheritdoc} * * @see FontInterface::setColor() */ public function setColor(mixed $color): FontInterface { $this->color = $color; return $this; } /** * {@inheritdoc} * * @see FontInterface::color() */ public function color(): mixed { return $this->color; } /** * {@inheritdoc} * * @see FontInterface::setStrokeColor() */ public function setStrokeColor(mixed $color): FontInterface { $this->strokeColor = $color; return $this; } /** * {@inheritdoc} * * @see FontInterface::strokeColor() */ public function strokeColor(): mixed { return $this->strokeColor; } /** * {@inheritdoc} * * @see FontInterface::setStrokeWidth() */ public function setStrokeWidth(int $width): FontInterface { if (!in_array($width, range(0, 10))) { throw new FontException( 'The stroke width must be in the range from 0 to 10.' ); } $this->strokeWidth = $width; return $this; } /** * {@inheritdoc} * * @see FontInterface::strokeWidth() */ public function strokeWidth(): int { return $this->strokeWidth; } /** * {@inheritdoc} * * @see FontInterface::hasStrokeEffect() */ public function hasStrokeEffect(): bool { return $this->strokeWidth > 0; } /** * {@inheritdoc} * * @see FontInterface::alignment() */ public function alignment(): string { return $this->alignment; } /** * {@inheritdoc} * * @see FontInterface::setAlignment() */ public function setAlignment(string $value): FontInterface { $this->alignment = $value; return $this; } /** * {@inheritdoc} * * @see FontInterface::valignment() */ public function valignment(): string { return $this->valignment; } /** * {@inheritdoc} * * @see FontInterface::setValignment() */ public function setValignment(string $value): FontInterface { $this->valignment = $value; return $this; } /** * {@inheritdoc} * * @see FontInterface::setLineHeight() */ public function setLineHeight(float $height): FontInterface { $this->lineHeight = $height; return $this; } /** * {@inheritdoc} * * @see FontInterface::lineHeight() */ public function lineHeight(): float { return $this->lineHeight; } /** * {@inheritdoc} * * @see FontInterface::setWrapWidth() */ public function setWrapWidth(?int $width): FontInterface { $this->wrapWidth = $width; return $this; } /** * {@inheritdoc} * * @see FontInterface::wrapWidth() */ public function wrapWidth(): ?int { return $this->wrapWidth; } } ================================================ FILE: src/Typography/FontFactory.php ================================================ font = is_a($init, FontInterface::class) ? $init : new Font(); if (is_callable($init)) { $init($this); } } /** * Set the filename of the font to be built */ public function filename(string $value): self { $this->font->setFilename($value); return $this; } /** * {@inheritdoc} * * @see self::filename() */ public function file(string $value): self { return $this->filename($value); } /** * Set outline stroke effect for the font to be built * * @throws FontException */ public function stroke(mixed $color, int $width = 1): self { $this->font->setStrokeWidth($width); $this->font->setStrokeColor($color); return $this; } /** * Set color for the font to be built */ public function color(mixed $value): self { $this->font->setColor($value); return $this; } /** * Set the size for the font to be built */ public function size(float $value): self { $this->font->setSize($value); return $this; } /** * Set the horizontal alignment of the font to be built */ public function align(string $value): self { $this->font->setAlignment($value); return $this; } /** * Set the vertical alignment of the font to be built */ public function valign(string $value): self { $this->font->setValignment($value); return $this; } /** * Set the line height of the font to be built */ public function lineHeight(float $value): self { $this->font->setLineHeight($value); return $this; } /** * Set the rotation angle of the font to be built */ public function angle(float $value): self { $this->font->setAngle($value); return $this; } /** * Set the maximum width of the text block to be built */ public function wrap(int $width): self { $this->font->setWrapWidth($width); return $this; } /** * Build font */ public function __invoke(): FontInterface { return $this->font; } } ================================================ FILE: src/Typography/Line.php ================================================ */ class Line implements IteratorAggregate, Countable, Stringable { /** * Segments (usually individual words including punctuation marks) of the line * * @var array */ protected array $segments = []; /** * Create new text line object with given text & position * * @return void */ public function __construct( ?string $text = null, protected PointInterface $position = new Point() ) { if (is_string($text)) { $this->segments = $this->wordsSeperatedBySpaces($text) ? explode(" ", $text) : mb_str_split($text); } } /** * Add word to current line */ public function add(string $word): self { $this->segments[] = $word; return $this; } /** * Returns Iterator * * @return Traversable */ public function getIterator(): Traversable { return new ArrayIterator($this->segments); } /** * Get Position of line */ public function position(): PointInterface { return $this->position; } /** * Set position of current line */ public function setPosition(PointInterface $point): self { $this->position = $point; return $this; } /** * Count segments (individual words including punctuation marks) of line */ public function count(): int { return count($this->segments); } /** * Count characters of line */ public function length(): int { return mb_strlen((string) $this); } /** * Dermine if words are sperarated by spaces in the written language of the given text */ private function wordsSeperatedBySpaces(string $text): bool { return 1 !== preg_match( '/[' . '\x{4E00}-\x{9FFF}' . // CJK Unified Ideographs (chinese) '\x{3400}-\x{4DBF}' . // CJK Unified Ideographs Extension A (chinese) '\x{3040}-\x{309F}' . // hiragana (japanese) '\x{30A0}-\x{30FF}' . // katakana (japanese) '\x{0E00}-\x{0E7F}' . // thai ']/u', $text ); } /** * Cast line to string */ public function __toString(): string { $string = implode("", $this->segments); if ($this->wordsSeperatedBySpaces($string)) { return implode(" ", $this->segments); } return $string; } } ================================================ FILE: src/Typography/TextBlock.php ================================================ push(new Line($line)); } } /** * Return array of lines in text block * * @return array */ public function lines(): array { return $this->items; } /** * Set lines of the text block * * @param array $lines */ public function setLines(array $lines): self { $this->items = $lines; return $this; } /** * Get line by given key */ public function line(mixed $key): ?Line { if (!array_key_exists($key, $this->lines())) { return null; } return $this->lines()[$key]; } /** * Return line with most characters of text block */ public function longestLine(): Line { $lines = $this->lines(); usort($lines, function (Line $a, Line $b): int { if ($a->length() === $b->length()) { return 0; } return $a->length() > $b->length() ? -1 : 1; }); return $lines[0]; } } ================================================ FILE: tests/BaseTestCase.php ================================================ channel(Red::class)->value(), $color->channel(Green::class)->value(), $color->channel(Blue::class)->value(), $color->channel(Alpha::class)->value(), ]) . ')'; return implode(' ', [ 'Failed asserting that color', $color, 'equals', 'rgba(' . $r . ', ' . $g . ', ' . $b . ', ' . $a . ')' ]); }; // build color channel value range $range = function (int $base, int $tolerance): array { return range(max($base - $tolerance, 0), min($base + $tolerance, 255)); }; $this->assertContains( $color->channel(Red::class)->value(), $range($r, $tolerance), $errorMessage($r, $g, $b, $a, $color) ); $this->assertContains( $color->channel(Green::class)->value(), $range($g, $tolerance), $errorMessage($r, $g, $b, $a, $color) ); $this->assertContains( $color->channel(Blue::class)->value(), $range($b, $tolerance), $errorMessage($r, $g, $b, $a, $color) ); $this->assertContains( $color->channel(Alpha::class)->value(), $range($a, $tolerance), $errorMessage($r, $g, $b, $a, $color) ); } protected function assertTransparency(ColorInterface $color): void { $this->assertInstanceOf(RgbColor::class, $color); $channel = $color->channel(Alpha::class); $this->assertEquals(0, $channel->value(), 'Detected color ' . $color . ' is not completely transparent.'); } protected function assertMediaType(string|array $allowed, string|EncodedImage $input): void { $pointer = fopen('php://temp', 'rw'); fwrite($pointer, (string) $input); rewind($pointer); $detected = mime_content_type($pointer); fclose($pointer); $allowed = is_string($allowed) ? [$allowed] : $allowed; $this->assertTrue( in_array($detected, $allowed), 'Detected media type "' . $detected . '" is not: ' . implode(', ', $allowed), ); } protected function assertMediaTypeBitmap(string|EncodedImage $input): void { $this->assertMediaType([ 'image/x-ms-bmp', 'image/bmp', 'bmp', 'ms-bmp', 'x-bitmap', 'x-bmp', 'x-ms-bmp', 'x-win-bitmap', 'x-windows-bmp', 'x-xbitmap', 'image/ms-bmp', 'image/x-bitmap', 'image/x-bmp', 'image/x-ms-bmp', 'image/x-win-bitmap', 'image/x-windows-bmp', 'image/x-xbitmap', ], $input); } } ================================================ FILE: tests/Feature/Gd/ConvertPngGif.php ================================================ read( $this->readTestImage('circle.png')->toGif() ); $this->assertTransparency($converted->pickColor(0, 0)); $this->assertColor(4, 2, 4, 255, $converted->pickColor(25, 25), 4); } } ================================================ FILE: tests/Feature/Imagick/ConvertPngGif.php ================================================ read( $this->readTestImage('circle.png')->toGif() ); $this->assertTransparency($converted->pickColor(0, 0)); $this->assertColor(4, 2, 4, 255, $converted->pickColor(25, 25), 4); } } ================================================ FILE: tests/Feature/Imagick/CropResizePngTest.php ================================================ readTestImage('tile.png'); $image->crop(100, 100); $image->resize(200, 200); $this->assertTransparency($image->pickColor(7, 22)); $this->assertTransparency($image->pickColor(22, 7)); } } ================================================ FILE: tests/GdTestCase.php ================================================ specialize(new FilePathImageDecoder())->decode( static::getTestResourcePath($filename) ); } public static function createTestImage(int $width, int $height): Image { $gd = imagecreatetruecolor($width, $height); imagefill($gd, 0, 0, imagecolorallocate($gd, 255, 0, 0)); return new Image( new Driver(), new Core([ new Frame($gd) ]) ); } public static function createTestAnimation(): Image { $gd1 = imagecreatetruecolor(3, 2); imagefill($gd1, 0, 0, imagecolorallocate($gd1, 255, 0, 0)); $gd2 = imagecreatetruecolor(3, 2); imagefill($gd2, 0, 0, imagecolorallocate($gd1, 0, 255, 0)); $gd3 = imagecreatetruecolor(3, 2); imagefill($gd3, 0, 0, imagecolorallocate($gd1, 0, 0, 255)); return new Image( new Driver(), new Core([ new Frame($gd1), new Frame($gd2), new Frame($gd3), ]) ); } } ================================================ FILE: tests/ImagickTestCase.php ================================================ specialize(new FilePathImageDecoder())->decode( static::getTestResourcePath($filename) ); } /** * Create test image with red (#ff0000) background * * @throws ImagickException */ public static function createTestImage(int $width, int $height): Image { $background = new ImagickPixel('rgb(255, 0, 0)'); $imagick = new Imagick(); $imagick->newImage($width, $height, $background, 'png'); $imagick->setType(Imagick::IMGTYPE_UNDEFINED); $imagick->setImageType(Imagick::IMGTYPE_UNDEFINED); $imagick->setColorspace(Imagick::COLORSPACE_SRGB); $imagick->setImageResolution(96, 96); $imagick->setImageBackgroundColor($background); return new Image( new Driver(), new Core($imagick) ); } public static function createTestAnimation(): Image { $imagick = new Imagick(); $imagick->setFormat('gif'); for ($i = 0; $i < 3; $i++) { $frame = new Imagick(); $frame->newImage(3, 2, new ImagickPixel('rgb(255, 0, 0)'), 'gif'); $frame->setImageDelay(10); $imagick->addImage($frame); } return new Image( new Driver(), new Core($imagick) ); } } ================================================ FILE: tests/Traits/CanDetectProgressiveJpeg.php ================================================ toFilePointer(); while (!feof($f)) { if (unpack('C', fread($f, 1))[1] !== 0xff) { return false; } $blockType = unpack('C', fread($f, 1))[1]; switch (true) { case $blockType == 0xd8: case $blockType >= 0xd0 && $blockType <= 0xd7: break; case $blockType == 0xc0: fclose($f); return false; case $blockType == 0xc2: fclose($f); return true; case $blockType == 0xd9: break 2; default: $blockSize = unpack('n', fread($f, 2))[1]; fseek($f, $blockSize - 2, SEEK_CUR); break; } } fclose($f); return false; } } ================================================ FILE: tests/Traits/CanInspectPngFormat.php ================================================ toFilePointer(); $contents = fread($f, 32); fclose($f); return ord($contents[28]) != 0; } /** * Try to detect PNG color type from given binary data */ private function pngColorType(EncodedImage $image): string { $data = $image->toString(); if (substr($data, 1, 3) !== 'PNG') { return 'unkown'; } $pos = strpos($data, 'IHDR'); $type = substr($data, $pos + 13, 1); return match (unpack('C', $type)[1]) { 0 => 'grayscale', 2 => 'truecolor', 3 => 'indexed', 4 => 'grayscale-alpha', 6 => 'truecolor-alpha', default => 'unknown', }; } } ================================================ FILE: tests/Unit/CollectionTest.php ================================================ assertInstanceOf(Collection::class, $collection); $collection = Collection::create(['foo', 'bar', 'baz']); $this->assertInstanceOf(Collection::class, $collection); } public function testIterator(): void { $collection = new Collection(['foo', 'bar', 'baz']); foreach ($collection as $key => $item) { switch ($key) { case 0: $this->assertEquals('foo', $item); break; case 1: $this->assertEquals('bar', $item); break; case 2: $this->assertEquals('baz', $item); break; } } } public function testCount(): void { $collection = new Collection(['foo', 'bar', 'baz']); $this->assertEquals(3, $collection->count()); $this->assertEquals(3, count($collection)); } public function testFilter(): void { $collection = new Collection(['foo', 'bar', 'baz']); $this->assertEquals(3, $collection->count()); $collection = $collection->filter(function ($text): bool { return substr($text, 0, 1) == 'b'; }); $this->assertEquals(2, $collection->count()); } public function testFirstLast(): void { $collection = new Collection(['foo', 'bar', 'baz']); $this->assertEquals('foo', $collection->first()); $this->assertEquals('baz', $collection->last()); $collection = new Collection(); $this->assertNull($collection->first()); $this->assertNull($collection->last()); } public function testPush(): void { $collection = new Collection(['foo', 'bar', 'baz']); $this->assertEquals(3, $collection->count()); $result = $collection->push('test'); $this->assertEquals(4, $collection->count()); $this->assertInstanceOf(Collection::class, $result); } public function testToArray(): void { $collection = new Collection(['foo', 'bar', 'baz']); $this->assertEquals(['foo', 'bar', 'baz'], $collection->toArray()); } public function testMap(): void { $collection = new Collection(['FOO', 'BAR', 'BAZ']); $mapped = $collection->map(function ($item) { return strtolower($item); }); $this->assertInstanceOf(Collection::class, $collection); $this->assertInstanceOf(Collection::class, $mapped); $this->assertEquals(['FOO', 'BAR', 'BAZ'], $collection->toArray()); $this->assertEquals(['foo', 'bar', 'baz'], $mapped->toArray()); } public function testGet(): void { // phpcs:ignore SlevomatCodingStandard.Arrays.DisallowPartiallyKeyed $collection = new Collection([ 'first', 'second', ['testx' => 'x'], 'foo' => 'foo_value', 'bar' => 'bar_value', 'baz' => [ 'test1' => '1', 'test2' => '2', 'test3' => [ 'example' => 'value' ] ] ]); $this->assertEquals('first', $collection->get(0)); $this->assertEquals('second', $collection->get(1)); $this->assertEquals('first', $collection->get('0')); $this->assertEquals('second', $collection->get('1')); $this->assertEquals('x', $collection->get('2.testx')); $this->assertEquals('foo_value', $collection->get('foo')); $this->assertEquals('bar_value', $collection->get('bar')); $this->assertEquals('1', $collection->get('baz.test1')); $this->assertEquals('2', $collection->get('baz.test2')); $this->assertEquals('value', $collection->get('baz.test3.example')); $this->assertEquals('value', $collection->get('baz.test3.example', 'default')); $this->assertEquals('default', $collection->get('baz.test3.no', 'default')); $this->assertEquals(['example' => 'value'], $collection->get('baz.test3')); } public function testGetAtPosition(): void { // phpcs:ignore SlevomatCodingStandard.Arrays.DisallowPartiallyKeyed $collection = new Collection([1, 2, 'foo' => 'bar']); $this->assertEquals(1, $collection->getAtPosition(0)); $this->assertEquals(2, $collection->getAtPosition(1)); $this->assertEquals('bar', $collection->getAtPosition(2)); $this->assertNull($collection->getAtPosition(3)); $this->assertEquals('default', $collection->getAtPosition(3, 'default')); } public function testGetAtPositionEmpty(): void { $collection = new Collection(); $this->assertNull($collection->getAtPosition()); $this->assertEquals('default', $collection->getAtPosition(3, 'default')); } public function testEmpty(): void { $collection = new Collection([1, 2, 3]); $this->assertEquals(3, $collection->count()); $result = $collection->empty(); $this->assertEquals(0, $collection->count()); $this->assertEquals(0, $result->count()); } public function testSlice(): void { $collection = new Collection(['a', 'b', 'c', 'd', 'e', 'f']); $this->assertEquals(6, $collection->count()); $result = $collection->slice(0, 3); $this->assertEquals(['a', 'b', 'c'], $collection->toArray()); $this->assertEquals(['a', 'b', 'c'], $result->toArray()); $this->assertEquals('a', $result->get(0)); $this->assertEquals('b', $result->get(1)); $this->assertEquals('c', $result->get(2)); $result = $collection->slice(2, 1); $this->assertEquals(['c'], $collection->toArray()); $this->assertEquals(['c'], $result->toArray()); $this->assertEquals('c', $result->get(0)); } public function testSliceOutOfBounds(): void { $collection = new Collection(['a', 'b', 'c']); $result = $collection->slice(6); $this->assertEquals(0, $result->count()); $this->assertEquals([], $result->toArray()); } } ================================================ FILE: tests/Unit/Colors/Cmyk/ChannelTest.php ================================================ assertInstanceOf(Cyan::class, $channel); $channel = new Cyan(value: 0); $this->assertInstanceOf(Cyan::class, $channel); $channel = new Cyan(normalized: 0); $this->assertInstanceOf(Cyan::class, $channel); $this->expectException(ColorException::class); new Cyan(); $this->expectException(ColorException::class); new Cyan(normalized: 2); } public function testConstructorFail(): void { $this->expectException(ColorException::class); new Cyan(200); } public function testToInt(): void { $channel = new Cyan(10); $this->assertEquals(10, $channel->toInt()); } public function testToString(): void { $channel = new Cyan(10); $this->assertEquals("10", $channel->toString()); $this->assertEquals("10", (string) $channel); } public function testValue(): void { $channel = new Cyan(10); $this->assertEquals(10, $channel->value()); } public function testNormalize(): void { $channel = new Cyan(100); $this->assertEquals(1, $channel->normalize()); $channel = new Cyan(0); $this->assertEquals(0, $channel->normalize()); $channel = new Cyan(20); $this->assertEquals(.2, $channel->normalize()); } public function testValidate(): void { $this->expectException(ColorException::class); new Cyan(101); $this->expectException(ColorException::class); new Cyan(-1); } } ================================================ FILE: tests/Unit/Colors/Cmyk/ColorTest.php ================================================ assertInstanceOf(Color::class, $color); } public function testCreate(): void { $color = Color::create('cmyk(10, 20, 30, 40)'); $this->assertInstanceOf(Color::class, $color); } public function testColorspace(): void { $color = new Color(0, 0, 0, 0); $this->assertInstanceOf(Colorspace::class, $color->colorspace()); } public function testChannels(): void { $color = new Color(10, 20, 30, 40); $this->assertIsArray($color->channels()); $this->assertCount(4, $color->channels()); } public function testChannel(): void { $color = new Color(10, 20, 30, 40); $channel = $color->channel(Cyan::class); $this->assertInstanceOf(Cyan::class, $channel); $this->assertEquals(10, $channel->value()); } public function testChannelNotFound(): void { $color = new Color(10, 20, 30, 30); $this->expectException(ColorException::class); $color->channel('none'); } public function testCyanMagentaYellowKey(): void { $color = new Color(10, 20, 30, 40); $this->assertInstanceOf(Cyan::class, $color->cyan()); $this->assertInstanceOf(Magenta::class, $color->magenta()); $this->assertInstanceOf(Yellow::class, $color->yellow()); $this->assertInstanceOf(Key::class, $color->key()); $this->assertEquals(10, $color->cyan()->value()); $this->assertEquals(20, $color->magenta()->value()); $this->assertEquals(30, $color->yellow()->value()); $this->assertEquals(40, $color->key()->value()); } public function testToArray(): void { $color = new Color(10, 20, 30, 40); $this->assertEquals([10, 20, 30, 40], $color->toArray()); } public function testToHex(): void { $color = new Color(0, 73, 100, 0); $this->assertEquals('ff4400', $color->toHex()); $this->assertEquals('#ff4400', $color->toHex('#')); } public function testIsGreyscale(): void { $color = new Color(0, 73, 100, 0); $this->assertFalse($color->isGreyscale()); $color = new Color(0, 0, 0, 50); $this->assertTrue($color->isGreyscale()); } public function testNormalize(): void { $color = new Color(100, 50, 20, 0); $this->assertEquals([1.0, 0.5, 0.2, 0.0], $color->normalize()); } public function testToString(): void { $color = new Color(100, 50, 20, 0); $this->assertEquals('cmyk(100%, 50%, 20%, 0%)', (string) $color); } public function testIsTransparent(): void { $color = new Color(100, 50, 50, 0); $this->assertFalse($color->isTransparent()); } public function testIsClear(): void { $color = new Color(0, 0, 0, 0); $this->assertFalse($color->isClear()); } public function testDebugInfo(): void { $info = (new Color(10, 20, 30, 40))->__debugInfo(); $this->assertEquals(10, $info['cyan']); $this->assertEquals(20, $info['magenta']); $this->assertEquals(30, $info['yellow']); $this->assertEquals(40, $info['key']); } } ================================================ FILE: tests/Unit/Colors/Cmyk/ColorspaceTest.php ================================================ colorFromNormalized([0, 1, 0, 1]); $this->assertInstanceOf(CmykColor::class, $result); $this->assertEquals(0, $result->channel(Cyan::class)->value()); $this->assertEquals(100, $result->channel(Magenta::class)->value()); $this->assertEquals(0, $result->channel(Yellow::class)->value()); $this->assertEquals(100, $result->channel(Key::class)->value()); } public function testImportRgbColor(): void { $colorspace = new Colorspace(); $result = $colorspace->importColor(new RgbColor(255, 0, 255)); $this->assertInstanceOf(CmykColor::class, $result); $this->assertEquals(0, $result->channel(Cyan::class)->value()); $this->assertEquals(100, $result->channel(Magenta::class)->value()); $this->assertEquals(0, $result->channel(Yellow::class)->value()); $this->assertEquals(0, $result->channel(Key::class)->value()); $result = $colorspace->importColor(new RgbColor(127, 127, 127)); $this->assertInstanceOf(CmykColor::class, $result); $this->assertEquals(0, $result->channel(Cyan::class)->value()); $this->assertEquals(0, $result->channel(Magenta::class)->value()); $this->assertEquals(0, $result->channel(Yellow::class)->value()); $this->assertEquals(50, $result->channel(Key::class)->value()); $result = $colorspace->importColor(new RgbColor(127, 127, 127, 85)); $this->assertInstanceOf(CmykColor::class, $result); $this->assertEquals(0, $result->channel(Cyan::class)->value()); $this->assertEquals(0, $result->channel(Magenta::class)->value()); $this->assertEquals(0, $result->channel(Yellow::class)->value()); $this->assertEquals(50, $result->channel(Key::class)->value()); } public function testImportHsvColor(): void { $colorspace = new Colorspace(); $result = $colorspace->importColor(new HsvColor(0, 0, 50)); $this->assertInstanceOf(CmykColor::class, $result); $this->assertEquals(0, $result->channel(Cyan::class)->value()); $this->assertEquals(0, $result->channel(Magenta::class)->value()); $this->assertEquals(0, $result->channel(Yellow::class)->value()); $this->assertEquals(50, $result->channel(Key::class)->value()); } public function testImportHslColor(): void { $colorspace = new Colorspace(); $result = $colorspace->importColor(new HslColor(300, 100, 50)); $this->assertInstanceOf(CmykColor::class, $result); $this->assertEquals(0, $result->channel(Cyan::class)->value()); $this->assertEquals(100, $result->channel(Magenta::class)->value()); $this->assertEquals(0, $result->channel(Yellow::class)->value()); $this->assertEquals(0, $result->channel(Key::class)->value()); $result = $colorspace->importColor(new HslColor(0, 0, 50)); $this->assertInstanceOf(CmykColor::class, $result); $this->assertEquals(0, $result->channel(Cyan::class)->value()); $this->assertEquals(0, $result->channel(Magenta::class)->value()); $this->assertEquals(0, $result->channel(Yellow::class)->value()); $this->assertEquals(50, $result->channel(Key::class)->value()); } } ================================================ FILE: tests/Unit/Colors/Cmyk/Decoders/StringColorDecoderTest.php ================================================ decode('cmyk(0,0,0,0)'); $this->assertInstanceOf(Color::class, $result); $this->assertEquals([0, 0, 0, 0], $result->toArray()); $result = $decoder->decode('cmyk(0, 100, 100, 0)'); $this->assertInstanceOf(Color::class, $result); $this->assertEquals([0, 100, 100, 0], $result->toArray()); $result = $decoder->decode('cmyk(0, 100, 100, 0)'); $this->assertInstanceOf(Color::class, $result); $this->assertEquals([0, 100, 100, 0], $result->toArray()); $result = $decoder->decode('cmyk(0%, 100%, 100%, 0%)'); $this->assertInstanceOf(Color::class, $result); $this->assertEquals([0, 100, 100, 0], $result->toArray()); } public function testDecodeInvalid(): void { $decoder = new StringColorDecoder(); $this->expectException(DecoderException::class); $decoder->decode(null); } } ================================================ FILE: tests/Unit/Colors/Hsl/ChannelTest.php ================================================ assertInstanceOf(Hue::class, $channel); $channel = new Hue(value: 0); $this->assertInstanceOf(Hue::class, $channel); $channel = new Hue(normalized: 0); $this->assertInstanceOf(Hue::class, $channel); $this->expectException(ColorException::class); new Hue(); $this->expectException(ColorException::class); new Hue(normalized: 2); } public function testConstructorFail(): void { $this->expectException(ColorException::class); new Hue(400); } public function testToInt(): void { $channel = new Hue(10); $this->assertEquals(10, $channel->toInt()); } public function testToString(): void { $channel = new Hue(10); $this->assertEquals("10", $channel->toString()); $this->assertEquals("10", (string) $channel); } public function testValue(): void { $channel = new Hue(10); $this->assertEquals(10, $channel->value()); } public function testNormalize(): void { $channel = new Hue(360); $this->assertEquals(1, $channel->normalize()); $channel = new Hue(180); $this->assertEquals(0.5, $channel->normalize()); $channel = new Hue(0); $this->assertEquals(0, $channel->normalize()); $channel = new Luminance(90); $this->assertEquals(.9, $channel->normalize()); } public function testValidate(): void { $this->expectException(ColorException::class); new Hue(361); $this->expectException(ColorException::class); new Hue(-1); $this->expectException(ColorException::class); new Saturation(101); $this->expectException(ColorException::class); new Saturation(-1); $this->expectException(ColorException::class); new Luminance(101); $this->expectException(ColorException::class); new Luminance(-1); } } ================================================ FILE: tests/Unit/Colors/Hsl/Channels/SaturationTest.php ================================================ assertEquals(0, $saturation->min()); $this->assertEquals(100, $saturation->max()); } } ================================================ FILE: tests/Unit/Colors/Hsl/ColorTest.php ================================================ assertInstanceOf(Color::class, $color); } public function testCreate(): void { $color = Color::create('hsl(10, 20, 30)'); $this->assertInstanceOf(Color::class, $color); } public function testColorspace(): void { $color = new Color(0, 0, 0); $this->assertInstanceOf(Colorspace::class, $color->colorspace()); } public function testChannels(): void { $color = new Color(10, 20, 30); $this->assertIsArray($color->channels()); $this->assertCount(3, $color->channels()); } public function testChannel(): void { $color = new Color(10, 20, 30); $channel = $color->channel(Hue::class); $this->assertInstanceOf(Hue::class, $channel); $this->assertEquals(10, $channel->value()); } public function testChannelNotFound(): void { $color = new Color(10, 20, 30); $this->expectException(ColorException::class); $color->channel('none'); } public function testHueSaturationLuminanceKey(): void { $color = new Color(10, 20, 30); $this->assertInstanceOf(Hue::class, $color->hue()); $this->assertInstanceOf(Saturation::class, $color->saturation()); $this->assertInstanceOf(Luminance::class, $color->luminance()); $this->assertEquals(10, $color->hue()->value()); $this->assertEquals(20, $color->saturation()->value()); $this->assertEquals(30, $color->luminance()->value()); } public function testToArray(): void { $color = new Color(10, 20, 30); $this->assertEquals([10, 20, 30], $color->toArray()); } public function testToHex(): void { $color = new Color(16, 100, 50); $this->assertEquals('ff4400', $color->toHex()); } public function testNormalize(): void { $color = new Color(180, 50, 25); $this->assertEquals([.5, 0.5, 0.25], $color->normalize()); } public function testToString(): void { $color = new Color(100, 50, 20, 0); $this->assertEquals('hsl(100, 50%, 20%)', (string) $color); } public function testIsGreyscale(): void { $color = new Color(0, 1, 0); $this->assertFalse($color->isGreyscale()); $color = new Color(1, 0, 0); $this->assertTrue($color->isGreyscale()); $color = new Color(0, 0, 1); $this->assertTrue($color->isGreyscale()); } public function testIsTransparent(): void { $color = new Color(0, 1, 0); $this->assertFalse($color->isTransparent()); } public function testIsClear(): void { $color = new Color(0, 1, 0); $this->assertFalse($color->isClear()); } public function testDebugInfo(): void { $info = (new Color(10, 20, 30))->__debugInfo(); $this->assertEquals(10, $info['hue']); $this->assertEquals(20, $info['saturation']); $this->assertEquals(30, $info['luminance']); } } ================================================ FILE: tests/Unit/Colors/Hsl/ColorspaceTest.php ================================================ colorFromNormalized([1, 0, 1]); $this->assertInstanceOf(HslColor::class, $result); $this->assertEquals(360, $result->channel(Hue::class)->value()); $this->assertEquals(0, $result->channel(Saturation::class)->value()); $this->assertEquals(100, $result->channel(Luminance::class)->value()); } public function testImportRgbColor(): void { $colorspace = new Colorspace(); $result = $colorspace->importColor(new RgbColor(255, 0, 255)); $this->assertInstanceOf(HslColor::class, $result); $this->assertEquals(300, $result->channel(Hue::class)->value()); $this->assertEquals(100, $result->channel(Saturation::class)->value()); $this->assertEquals(50, $result->channel(Luminance::class)->value()); $result = $colorspace->importColor(new RgbColor(127, 127, 127)); $this->assertInstanceOf(HslColor::class, $result); $this->assertEquals(0, $result->channel(Hue::class)->value()); $this->assertEquals(0, $result->channel(Saturation::class)->value()); $this->assertEquals(50, $result->channel(Luminance::class)->value()); $result = $colorspace->importColor(new RgbColor(255, 0, 0, 85)); $this->assertInstanceOf(HslColor::class, $result); $this->assertEquals(0, $result->channel(Hue::class)->value()); $this->assertEquals(100, $result->channel(Saturation::class)->value()); $this->assertEquals(50, $result->channel(Luminance::class)->value()); } public function testImportCmykColor(): void { $colorspace = new Colorspace(); $result = $colorspace->importColor(new CmykColor(0, 100, 0, 0)); $this->assertInstanceOf(HslColor::class, $result); $this->assertEquals(300, $result->channel(Hue::class)->value()); $this->assertEquals(100, $result->channel(Saturation::class)->value()); $this->assertEquals(50, $result->channel(Luminance::class)->value()); $result = $colorspace->importColor(new CmykColor(0, 0, 0, 50)); $this->assertInstanceOf(HslColor::class, $result); $this->assertEquals(0, $result->channel(Hue::class)->value()); $this->assertEquals(0, $result->channel(Saturation::class)->value()); $this->assertEquals(50, $result->channel(Luminance::class)->value()); } public function testImportHsvColor(): void { $colorspace = new Colorspace(); $result = $colorspace->importColor(new HsvColor(300, 100, 100)); $this->assertInstanceOf(HslColor::class, $result); $this->assertEquals(300, $result->channel(Hue::class)->value()); $this->assertEquals(100, $result->channel(Saturation::class)->value()); $this->assertEquals(50, $result->channel(Luminance::class)->value()); $result = $colorspace->importColor(new HsvColor(0, 0, 50)); $this->assertInstanceOf(HslColor::class, $result); $this->assertEquals(0, $result->channel(Hue::class)->value()); $this->assertEquals(0, $result->channel(Saturation::class)->value()); $this->assertEquals(50, $result->channel(Luminance::class)->value()); } } ================================================ FILE: tests/Unit/Colors/Hsl/Decoders/StringColorDecoderTest.php ================================================ */ #[DataProvider('decodeDataProvier')] public function testDecode(string $input, string $classname, array $channelValues): void { $decoder = new StringColorDecoder(); $result = $decoder->decode($input); $this->assertInstanceOf($classname, $result); $this->assertEquals($channelValues, $result->toArray()); } public static function decodeDataProvier(): Generator { yield [ 'hsl(0,0,0)', Color::class, [0, 0, 0], ]; yield [ 'hsl(0, 100, 50)', Color::class, [0, 100, 50], ]; yield [ 'hsl(360, 100, 50)', Color::class, [360, 100, 50], ]; yield [ 'hsl(180, 100%, 50%)', Color::class, [180, 100, 50], ]; } } ================================================ FILE: tests/Unit/Colors/Hsv/ChannelTest.php ================================================ assertInstanceOf(Hue::class, $channel); $channel = new Hue(value: 0); $this->assertInstanceOf(Hue::class, $channel); $channel = new Hue(normalized: 0); $this->assertInstanceOf(Hue::class, $channel); $this->expectException(ColorException::class); new Hue(); $this->expectException(ColorException::class); new Hue(normalized: 2); } public function testConstructorFail(): void { $this->expectException(ColorException::class); new Hue(400); } public function testToInt(): void { $channel = new Hue(10); $this->assertEquals(10, $channel->toInt()); } public function testToString(): void { $channel = new Hue(10); $this->assertEquals("10", $channel->toString()); $this->assertEquals("10", (string) $channel); } public function testValue(): void { $channel = new Hue(10); $this->assertEquals(10, $channel->value()); } public function testNormalize(): void { $channel = new Hue(360); $this->assertEquals(1, $channel->normalize()); $channel = new Hue(180); $this->assertEquals(0.5, $channel->normalize()); $channel = new Hue(0); $this->assertEquals(0, $channel->normalize()); $channel = new Hue(90); $this->assertEquals(.25, $channel->normalize()); } public function testValidate(): void { $this->expectException(ColorException::class); new Hue(361); $this->expectException(ColorException::class); new Hue(-1); $this->expectException(ColorException::class); new Saturation(101); $this->expectException(ColorException::class); new Saturation(-1); $this->expectException(ColorException::class); new Value(101); $this->expectException(ColorException::class); new Value(-1); } } ================================================ FILE: tests/Unit/Colors/Hsv/Channels/SaturationTest.php ================================================ assertEquals(0, $saturation->min()); $this->assertEquals(100, $saturation->max()); } } ================================================ FILE: tests/Unit/Colors/Hsv/Channels/ValueTest.php ================================================ assertEquals(0, $saturation->min()); $this->assertEquals(100, $saturation->max()); } } ================================================ FILE: tests/Unit/Colors/Hsv/ColorTest.php ================================================ assertInstanceOf(Color::class, $color); } public function testCreate(): void { $color = Color::create('hsv(10, 20, 30)'); $this->assertInstanceOf(Color::class, $color); } public function testColorspace(): void { $color = new Color(0, 0, 0); $this->assertInstanceOf(Colorspace::class, $color->colorspace()); } public function testChannels(): void { $color = new Color(10, 20, 30); $this->assertIsArray($color->channels()); $this->assertCount(3, $color->channels()); } public function testChannel(): void { $color = new Color(10, 20, 30); $channel = $color->channel(Hue::class); $this->assertInstanceOf(Hue::class, $channel); $this->assertEquals(10, $channel->value()); } public function testChannelNotFound(): void { $color = new Color(10, 20, 30); $this->expectException(ColorException::class); $color->channel('none'); } public function testHueSaturationValueKey(): void { $color = new Color(10, 20, 30); $this->assertInstanceOf(Hue::class, $color->hue()); $this->assertInstanceOf(Saturation::class, $color->saturation()); $this->assertInstanceOf(Value::class, $color->value()); $this->assertEquals(10, $color->hue()->value()); $this->assertEquals(20, $color->saturation()->value()); $this->assertEquals(30, $color->value()->value()); } public function testToArray(): void { $color = new Color(10, 20, 30); $this->assertEquals([10, 20, 30], $color->toArray()); } public function testToHex(): void { $color = new Color(16, 100, 100); $this->assertEquals('ff4400', $color->toHex()); } public function testNormalize(): void { $color = new Color(180, 50, 25); $this->assertEquals([.5, 0.5, 0.25], $color->normalize()); } public function testToString(): void { $color = new Color(100, 50, 20, 0); $this->assertEquals('hsv(100, 50%, 20%)', (string) $color); } public function testIsGreyscale(): void { $color = new Color(0, 1, 0); $this->assertFalse($color->isGreyscale()); $color = new Color(1, 0, 0); $this->assertTrue($color->isGreyscale()); $color = new Color(0, 0, 1); $this->assertTrue($color->isGreyscale()); } public function testIsTransparent(): void { $color = new Color(1, 0, 0); $this->assertFalse($color->isTransparent()); } public function testIsClear(): void { $color = new Color(0, 1, 0); $this->assertFalse($color->isClear()); } public function testDebugInfo(): void { $info = (new Color(10, 20, 30))->__debugInfo(); $this->assertEquals(10, $info['hue']); $this->assertEquals(20, $info['saturation']); $this->assertEquals(30, $info['value']); } } ================================================ FILE: tests/Unit/Colors/Hsv/ColorspaceTest.php ================================================ colorFromNormalized([1, 0, 1]); $this->assertInstanceOf(HsvColor::class, $result); $this->assertEquals(360, $result->channel(Hue::class)->value()); $this->assertEquals(0, $result->channel(Saturation::class)->value()); $this->assertEquals(100, $result->channel(Value::class)->value()); } public function testImportRgbColor(): void { $colorspace = new Colorspace(); $result = $colorspace->importColor(new RgbColor(255, 0, 255)); $this->assertInstanceOf(HsvColor::class, $result); $this->assertEquals(300, $result->channel(Hue::class)->value()); $this->assertEquals(100, $result->channel(Saturation::class)->value()); $this->assertEquals(100, $result->channel(Value::class)->value()); $result = $colorspace->importColor(new RgbColor(127, 127, 127)); $this->assertInstanceOf(HsvColor::class, $result); $this->assertEquals(0, $result->channel(Hue::class)->value()); $this->assertEquals(0, $result->channel(Saturation::class)->value()); $this->assertEquals(50, $result->channel(Value::class)->value()); $result = $colorspace->importColor(new RgbColor(127, 127, 127, 85)); $this->assertInstanceOf(HsvColor::class, $result); $this->assertEquals(0, $result->channel(Hue::class)->value()); $this->assertEquals(0, $result->channel(Saturation::class)->value()); $this->assertEquals(50, $result->channel(Value::class)->value()); } public function testImportCmykColor(): void { $colorspace = new Colorspace(); $result = $colorspace->importColor(new CmykColor(0, 100, 0, 0)); $this->assertInstanceOf(HsvColor::class, $result); $this->assertEquals(300, $result->channel(Hue::class)->value()); $this->assertEquals(100, $result->channel(Saturation::class)->value()); $this->assertEquals(100, $result->channel(Value::class)->value()); $result = $colorspace->importColor(new CmykColor(0, 0, 0, 50)); $this->assertInstanceOf(HsvColor::class, $result); $this->assertEquals(0, $result->channel(Hue::class)->value()); $this->assertEquals(0, $result->channel(Saturation::class)->value()); $this->assertEquals(50, $result->channel(Value::class)->value()); } public function testImportHslColor(): void { $colorspace = new Colorspace(); $result = $colorspace->importColor(new HslColor(300, 100, 50)); $this->assertInstanceOf(HsvColor::class, $result); $this->assertEquals(300, $result->channel(Hue::class)->value()); $this->assertEquals(100, $result->channel(Saturation::class)->value()); $this->assertEquals(100, $result->channel(Value::class)->value()); $result = $colorspace->importColor(new HslColor(0, 0, 50)); $this->assertInstanceOf(HsvColor::class, $result); $this->assertEquals(0, $result->channel(Hue::class)->value()); $this->assertEquals(0, $result->channel(Saturation::class)->value()); $this->assertEquals(50, $result->channel(Value::class)->value()); } } ================================================ FILE: tests/Unit/Colors/Hsv/Decoders/StringColorDecoderTest.php ================================================ */ #[DataProvider('decodeDataProvier')] public function testDecodeHsv(string $input, string $classname, array $channelValues): void { $decoder = new StringColorDecoder(); $result = $decoder->decode($input); $this->assertInstanceOf($classname, $result); $this->assertEquals($channelValues, $result->toArray()); } public static function decodeDataProvier(): Generator { yield [ 'hsv(0,0,0)', Color::class, [0, 0, 0], ]; yield [ 'hsv(0, 100, 100)', Color::class, [0, 100, 100], ]; yield [ 'hsv(360, 100, 100)', Color::class, [360, 100, 100], ]; yield [ 'hsv(180, 100%, 100%)', Color::class, [180, 100, 100], ]; yield [ 'hsb(0,0,0)', Color::class, [0, 0, 0], ]; yield [ 'hsb(0, 100, 100)', Color::class, [0, 100, 100], ]; yield [ 'hsb(360, 100, 100)', Color::class, [360, 100, 100], ]; yield [ 'hsb(180, 100%, 100%)', Color::class, [180, 100, 100], ]; } } ================================================ FILE: tests/Unit/Colors/ProfileTest.php ================================================ getTestResourcePath()); $this->assertInstanceOf(Profile::class, $profile); $this->assertTrue($profile->size() > 0); } } ================================================ FILE: tests/Unit/Colors/Rgb/ChannelTest.php ================================================ assertInstanceOf(Channel::class, $channel); $channel = new Channel(value: 0); $this->assertInstanceOf(Channel::class, $channel); $channel = new Channel(normalized: 0); $this->assertInstanceOf(Channel::class, $channel); $this->expectException(ColorException::class); new Channel(); $this->expectException(ColorException::class); new Channel(normalized: 2); } public function testConstructorFail(): void { $this->expectException(ColorException::class); new Channel(300); } public function testToInt(): void { $channel = new Channel(255); $this->assertEquals(255, $channel->toInt()); } public function testToString(): void { $channel = new Channel(10); $this->assertEquals("10", $channel->toString()); $this->assertEquals("10", (string) $channel); } public function testValue(): void { $channel = new Channel(10); $this->assertEquals(10, $channel->value()); } public function testNormalize(): void { $channel = new Channel(255); $this->assertEquals(1, $channel->normalize()); $channel = new Channel(0); $this->assertEquals(0, $channel->normalize()); $channel = new Channel(51); $this->assertEquals(.2, $channel->normalize()); } public function testValidate(): void { $this->expectException(ColorException::class); new Channel(256); $this->expectException(ColorException::class); new Channel(-1); } } ================================================ FILE: tests/Unit/Colors/Rgb/Channels/AlphaTest.php ================================================ assertEquals('0.333333', $alpha->toString()); $this->assertEquals('0.333333', (string) $alpha); } } ================================================ FILE: tests/Unit/Colors/Rgb/ColorTest.php ================================================ assertInstanceOf(Color::class, $color); $color = new Color(0, 0, 0, 0); $this->assertInstanceOf(Color::class, $color); } public function testCreate(): void { $color = Color::create('ccc'); $this->assertInstanceOf(Color::class, $color); $this->assertEquals([204, 204, 204, 255], $color->toArray()); $color = Color::create('rgba(10, 20, 30, .2)'); $this->assertInstanceOf(Color::class, $color); $this->assertEquals([10, 20, 30, 51], $color->toArray()); } public function testColorspace(): void { $color = new Color(0, 0, 0); $this->assertInstanceOf(RgbColorspace::class, $color->colorspace()); } public function testChannels(): void { $color = new Color(10, 20, 30); $this->assertIsArray($color->channels()); $this->assertCount(4, $color->channels()); } public function testChannel(): void { $color = new Color(10, 20, 30); $channel = $color->channel(Red::class); $this->assertInstanceOf(Red::class, $channel); $this->assertEquals(10, $channel->value()); } public function testChannelNotFound(): void { $color = new Color(10, 20, 30); $this->expectException(ColorException::class); $color->channel('none'); } public function testRedGreenBlue(): void { $color = new Color(10, 20, 30); $this->assertInstanceOf(Red::class, $color->red()); $this->assertInstanceOf(Green::class, $color->green()); $this->assertInstanceOf(Blue::class, $color->blue()); $this->assertEquals(10, $color->red()->value()); $this->assertEquals(20, $color->green()->value()); $this->assertEquals(30, $color->blue()->value()); } public function testToArray(): void { $color = new Color(10, 20, 30); $this->assertEquals([10, 20, 30, 255], $color->toArray()); } public function testToHex(): void { $color = new Color(181, 55, 23); $this->assertEquals('b53717', $color->toHex()); $this->assertEquals('#b53717', $color->toHex('#')); $color = new Color(181, 55, 23, 51); $this->assertEquals('b5371733', $color->toHex()); } public function testNormalize(): void { $color = new Color(255, 0, 51); $this->assertEquals([1.0, 0.0, 0.2, 1.0], $color->normalize()); } public function testToString(): void { $color = new Color(181, 55, 23); $this->assertEquals('rgb(181, 55, 23)', (string) $color); } public function testConvertTo(): void { $color = new Color(0, 0, 0); $converted = $color->convertTo(CmykColorspace::class); $this->assertInstanceOf(CmykColor::class, $converted); $this->assertEquals([0, 0, 0, 100], $converted->toArray()); $color = new Color(255, 255, 255); $converted = $color->convertTo(CmykColorspace::class); $this->assertInstanceOf(CmykColor::class, $converted); $this->assertEquals([0, 0, 0, 0], $converted->toArray()); $color = new Color(255, 0, 0); $converted = $color->convertTo(CmykColorspace::class); $this->assertInstanceOf(CmykColor::class, $converted); $this->assertEquals([0, 100, 100, 0], $converted->toArray()); $color = new Color(255, 0, 255); $converted = $color->convertTo(CmykColorspace::class); $this->assertInstanceOf(CmykColor::class, $converted); $this->assertEquals([0, 100, 0, 0], $converted->toArray()); $color = new Color(255, 255, 0); $converted = $color->convertTo(CmykColorspace::class); $this->assertInstanceOf(CmykColor::class, $converted); $this->assertEquals([0, 0, 100, 0], $converted->toArray()); $color = new Color(255, 204, 204); $converted = $color->convertTo(CmykColorspace::class); $this->assertInstanceOf(CmykColor::class, $converted); $this->assertEquals([0, 20, 20, 0], $converted->toArray()); } public function testIsGreyscale(): void { $color = new Color(255, 0, 100); $this->assertFalse($color->isGreyscale()); $color = new Color(50, 50, 50); $this->assertTrue($color->isGreyscale()); } public function testIsTransparent(): void { $color = new Color(255, 255, 255); $this->assertFalse($color->isTransparent()); $color = new Color(255, 255, 255, 255); $this->assertFalse($color->isTransparent()); $color = new Color(255, 255, 255, 85); $this->assertTrue($color->isTransparent()); $color = new Color(255, 255, 255, 0); $this->assertTrue($color->isTransparent()); } public function testIsClear(): void { $color = new Color(255, 255, 255); $this->assertFalse($color->isClear()); $color = new Color(255, 255, 255, 255); $this->assertFalse($color->isClear()); $color = new Color(255, 255, 255, 85); $this->assertFalse($color->isClear()); $color = new Color(255, 255, 255, 0); $this->assertTrue($color->isClear()); } public function testDebugInfo(): void { $info = (new Color(10, 20, 30, 40))->__debugInfo(); $this->assertEquals(10, $info['red']); $this->assertEquals(20, $info['green']); $this->assertEquals(30, $info['blue']); $this->assertEquals(40, $info['alpha']); } } ================================================ FILE: tests/Unit/Colors/Rgb/ColorspaceTest.php ================================================ colorFromNormalized([1, 0, 1, 1]); $this->assertInstanceOf(RgbColor::class, $result); $this->assertEquals(255, $result->channel(Red::class)->value()); $this->assertEquals(0, $result->channel(Green::class)->value()); $this->assertEquals(255, $result->channel(Blue::class)->value()); $this->assertEquals(255, $result->channel(Alpha::class)->value()); } public function testImportCmykColor(): void { $colorspace = new Colorspace(); $result = $colorspace->importColor(new CmykColor(0, 100, 0, 0)); $this->assertInstanceOf(RgbColor::class, $result); $this->assertEquals(255, $result->channel(Red::class)->value()); $this->assertEquals(0, $result->channel(Green::class)->value()); $this->assertEquals(255, $result->channel(Blue::class)->value()); $result = $colorspace->importColor(new CmykColor(0, 0, 0, 50)); $this->assertInstanceOf(RgbColor::class, $result); $this->assertEquals(127, $result->channel(Red::class)->value()); $this->assertEquals(127, $result->channel(Green::class)->value()); $this->assertEquals(127, $result->channel(Blue::class)->value()); } public function testImportHsvColor(): void { $colorspace = new Colorspace(); $result = $colorspace->importColor(new HsvColor(300, 100, 100)); $this->assertInstanceOf(RgbColor::class, $result); $this->assertEquals(255, $result->channel(Red::class)->value()); $this->assertEquals(0, $result->channel(Green::class)->value()); $this->assertEquals(255, $result->channel(Blue::class)->value()); $result = $colorspace->importColor(new HsvColor(0, 0, 50)); $this->assertInstanceOf(RgbColor::class, $result); $this->assertEquals(128, $result->channel(Red::class)->value()); $this->assertEquals(128, $result->channel(Green::class)->value()); $this->assertEquals(128, $result->channel(Blue::class)->value()); } public function testImportHslColor(): void { $colorspace = new Colorspace(); $result = $colorspace->importColor(new HslColor(300, 100, 50)); $this->assertInstanceOf(RgbColor::class, $result); $this->assertEquals(255, $result->channel(Red::class)->value()); $this->assertEquals(0, $result->channel(Green::class)->value()); $this->assertEquals(255, $result->channel(Blue::class)->value()); $result = $colorspace->importColor(new HslColor(0, 0, 50)); $this->assertInstanceOf(RgbColor::class, $result); $this->assertEquals(128, $result->channel(Red::class)->value()); $this->assertEquals(128, $result->channel(Green::class)->value()); $this->assertEquals(128, $result->channel(Blue::class)->value()); } } ================================================ FILE: tests/Unit/Colors/Rgb/Decoders/HexColorDecoderTest.php ================================================ */ #[DataProvider('decodeDataProvier')] public function testDecode(string $input, string $classname, array $channelValues): void { $decoder = new HexColorDecoder(); $result = $decoder->decode($input); $this->assertInstanceOf($classname, $result); $this->assertEquals($channelValues, $result->toArray()); } public static function decodeDataProvier(): Generator { yield [ 'ccc', Color::class, [204, 204, 204, 255] ]; yield [ 'ccff33', Color::class, [204, 255, 51, 255], ]; yield [ '#ccc', Color::class, [204, 204, 204, 255], ]; yield [ 'cccccc', Color::class, [204, 204, 204, 255], ]; yield [ '#cccccc', Color::class, [204, 204, 204, 255], ]; yield [ '#ccccccff', Color::class, [204, 204, 204, 255], ]; yield [ '#cccf', Color::class, [204, 204, 204, 255], ]; yield [ 'ccccccff', Color::class, [204, 204, 204, 255], ]; yield [ 'cccf', Color::class, [204, 204, 204, 255], ]; yield [ '#b53717aa', Color::class, [181, 55, 23, 170], ]; } } ================================================ FILE: tests/Unit/Colors/Rgb/Decoders/HtmlColornameDecoderTest.php ================================================ */ #[DataProvider('decodeDataProvier')] public function testDecode(string $input, string $classname, array $channelValues): void { $decoder = new HtmlColornameDecoder(); $result = $decoder->decode($input); $this->assertInstanceOf($classname, $result); $this->assertEquals($channelValues, $result->toArray()); } public static function decodeDataProvier(): Generator { yield [ 'salmon', Color::class, [250, 128, 114, 255], ]; yield [ 'khaki', Color::class, [240, 230, 140, 255], ]; yield [ 'peachpuff', Color::class, [255, 218, 185, 255], ]; } } ================================================ FILE: tests/Unit/Colors/Rgb/Decoders/StringColorDecoderTest.php ================================================ */ #[DataProvider('decodeDataProvier')] public function testDecode(string $input, string $classname, array $channelValues): void { $decoder = new StringColorDecoder(); $result = $decoder->decode($input); $this->assertInstanceOf($classname, $result); $this->assertEquals($channelValues, $result->toArray()); } public static function decodeDataProvier(): Generator { yield [ 'rgb(204, 204, 204)', Color::class, [204, 204, 204, 255], ]; yield [ 'rgb(204,204,204)', Color::class, [204, 204, 204, 255], ]; yield [ 'rgb(100%,20%,0%)', Color::class, [255, 51, 0, 255], ]; yield [ 'rgb(100%,19.8064%,0.1239483%)', Color::class, [255, 51, 0, 255], ]; yield [ 'rgba(204, 204, 204, 1)', Color::class, [204, 204, 204, 255], ]; yield [ 'rgba(204,204,204,.2)', Color::class, [204, 204, 204, 51], ]; yield [ 'rgba(204,204,204,0.2)', Color::class, [204, 204, 204, 51], ]; yield [ 'srgb(255, 0, 0)', Color::class, [255, 0, 0, 255], ]; } } ================================================ FILE: tests/Unit/ConfigTest.php ================================================ assertInstanceOf(Config::class, $config); $this->assertTrue($config->autoOrientation); $this->assertTrue($config->decodeAnimation); $this->assertEquals('ffffff', $config->blendingColor); $config = new Config( autoOrientation: false, decodeAnimation: false, blendingColor: 'f00', strip: true, ); $this->assertInstanceOf(Config::class, $config); $this->assertFalse($config->autoOrientation); $this->assertFalse($config->decodeAnimation); $this->assertTrue($config->strip); $this->assertEquals('f00', $config->blendingColor); } public function testGetSetOptions(): void { $config = new Config(); $this->assertTrue($config->autoOrientation); $this->assertTrue($config->decodeAnimation); $this->assertFalse($config->strip); $this->assertEquals('ffffff', $config->blendingColor); $result = $config->setOptions( autoOrientation: false, decodeAnimation: false, blendingColor: 'f00', strip: true, ); $this->assertFalse($config->autoOrientation); $this->assertFalse($config->decodeAnimation); $this->assertEquals('f00', $config->blendingColor); $this->assertFalse($result->autoOrientation); $this->assertFalse($result->decodeAnimation); $this->assertTrue($result->strip); $this->assertEquals('f00', $result->blendingColor); $result = $config->setOptions(blendingColor: '000'); $this->assertFalse($config->autoOrientation); $this->assertFalse($config->decodeAnimation); $this->assertTrue($config->strip); $this->assertEquals('000', $config->blendingColor); $this->assertFalse($result->autoOrientation); $this->assertFalse($result->decodeAnimation); $this->assertTrue($result->strip); $this->assertEquals('000', $result->blendingColor); } public function testSetOptionsWithArray(): void { $config = new Config(); $result = $config->setOptions([ 'autoOrientation' => false, 'decodeAnimation' => false, 'blendingColor' => 'f00', 'strip' => true, ]); $this->assertFalse($config->autoOrientation); $this->assertFalse($config->decodeAnimation); $this->assertTrue($config->strip); $this->assertEquals('f00', $config->blendingColor); $this->assertFalse($result->autoOrientation); $this->assertFalse($result->decodeAnimation); $this->assertTrue($result->strip); $this->assertEquals('f00', $result->blendingColor); } } ================================================ FILE: tests/Unit/Drivers/AbstractDecoderTest.php ================================================ assertFalse($decoder->isGifFormat($this->getTestResourceData('exif.jpg'))); $this->assertTrue($decoder->isGifFormat($this->getTestResourceData('red.gif'))); } public function testIsFile(): void { $decoder = Mockery::mock(AbstractDecoder::class); $this->assertTrue($decoder->isFile($this->getTestResourcePath())); $this->assertFalse($decoder->isFile('non-existent-file')); $this->assertFalse($decoder->isFile(new stdClass())); $this->assertFalse($decoder->isFile(str_repeat('o', PHP_MAXPATHLEN + 1))); $this->assertFalse($decoder->isFile(__DIR__)); } public function testExtractExifDataFromBinary(): void { $source = $this->getTestResourceData('exif.jpg'); $pointer = $this->getTestResourcePointer('exif.jpg'); $decoder = Mockery::mock(AbstractDecoder::class); $decoder->shouldReceive('buildFilePointer')->with($source)->andReturn($pointer); $result = $decoder->extractExifData($source); $this->assertInstanceOf(CollectionInterface::class, $result); $this->assertEquals('Oliver Vogel', $result->get('IFD0.Artist')); } public function testExtractExifDataFromPath(): void { $decoder = Mockery::mock(AbstractDecoder::class); $result = $decoder->extractExifData($this->getTestResourcePath('exif.jpg')); $this->assertInstanceOf(CollectionInterface::class, $result); $this->assertEquals('Oliver Vogel', $result->get('IFD0.Artist')); } public function testParseDataUri(): void { $decoder = new class () extends AbstractDecoder { public function parse(mixed $input): object { return parent::parseDataUri($input); } public function decode(mixed $input): ImageInterface|ColorInterface { throw new Exception(''); } }; $result = $decoder->parse( 'data:image/gif;foo=bar;base64,R0lGODdhAwADAKIAAAQyrKTy/ByS7AQytLT2/AAAAAAAAAAAACwAAAAAAwADAAADBhgU0gMgAQA7' ); $this->assertTrue($result->isValid()); $this->assertEquals('image/gif', $result->mediaType()); $this->assertTrue($result->hasMediaType()); $this->assertTrue($result->isBase64Encoded()); $this->assertEquals( 'R0lGODdhAwADAKIAAAQyrKTy/ByS7AQytLT2/AAAAAAAAAAAACwAAAAAAwADAAADBhgU0gMgAQA7', $result->data() ); $result = $decoder->parse('data:text/plain;charset=utf-8,test'); $this->assertTrue($result->isValid()); $this->assertEquals('text/plain', $result->mediaType()); $this->assertTrue($result->hasMediaType()); $this->assertFalse($result->isBase64Encoded()); $this->assertEquals('test', $result->data()); $result = $decoder->parse('data:;charset=utf-8,'); $this->assertTrue($result->isValid()); $this->assertNull($result->mediaType()); $this->assertFalse($result->hasMediaType()); $this->assertFalse($result->isBase64Encoded()); $this->assertNull($result->data()); } public function testIsValidBase64(): void { $decoder = new class () extends AbstractDecoder { public function isValid(mixed $input): bool { return parent::isValidBase64($input); } public function decode(mixed $input): ImageInterface|ColorInterface { throw new Exception(''); } }; $this->assertTrue( $decoder->isValid('R0lGODdhAwADAKIAAAQyrKTy/ByS7AQytLT2/AAAAAAAAAAAACwAAAAAAwADAAADBhgU0gMgAQA7') ); $this->assertFalse( $decoder->isValid('foo') ); $this->assertFalse( $decoder->isValid(new stdClass()) ); } } ================================================ FILE: tests/Unit/Drivers/AbstractEncoderTest.php ================================================ makePartial(); $image = Mockery::mock(ImageInterface::class); $encoded = Mockery::mock(EncodedImage::class); $image->shouldReceive('encode')->andReturn($encoded); $result = $encoder->encode($image); $this->assertInstanceOf(EncodedImage::class, $result); } } ================================================ FILE: tests/Unit/Drivers/AbstractFontProcessorTest.php ================================================ getTestResourcePath('test.ttf'))) ->setWrapWidth(20) ->setSize(50) ->setLineHeight(1.25) ->setAlignment('center'); $processor = Mockery::mock(AbstractFontProcessor::class)->makePartial(); $processor ->shouldReceive('boxSize') ->with('T', $font) ->andReturn(new Rectangle(12, 6)); $processor ->shouldReceive('boxSize') ->with('Hy', $font) ->andReturn(new Rectangle(24, 6)); $processor ->shouldReceive('boxSize') ->with('AAAA', $font) ->andReturn(new Rectangle(24, 6, new Point(1000, 0))); $processor ->shouldReceive('boxSize') ->with('AAAA BBBB', $font) ->andReturn(new Rectangle(24, 6)); $processor ->shouldReceive('boxSize') ->with('BBBB', $font) ->andReturn(new Rectangle(24, 6, new Point(2000, 0))); $processor ->shouldReceive('boxSize') ->with('BBBB CCCC', $font) ->andReturn(new Rectangle(24, 6)); $processor ->shouldReceive('boxSize') ->with('CCCC', $font) ->andReturn(new Rectangle(24, 6, new Point(3000, 0))); $processor ->shouldReceive('boxSize') ->with($text, $font) ->andReturn(new Rectangle(100, 25, new Point(10, 0))); $block = $processor->textBlock($text, $font, new Point(0, 0)); $this->assertInstanceOf(TextBlock::class, $block); $this->assertEquals(3, $block->count()); $this->assertEquals(-512, $block->getAtPosition(0)->position()->x()); $this->assertEquals(-16, $block->getAtPosition(0)->position()->y()); $this->assertEquals(-1012, $block->getAtPosition(1)->position()->x()); $this->assertEquals(-8, $block->getAtPosition(1)->position()->y()); $this->assertEquals(-1512, $block->getAtPosition(2)->position()->x()); $this->assertEquals(0, $block->getAtPosition(2)->position()->y()); } public function testNativeFontSize(): void { $font = (new Font($this->getTestResourcePath('test.ttf')))->setSize(32); $processor = Mockery::mock(AbstractFontProcessor::class)->makePartial(); $this->assertEquals(32, $processor->nativeFontSize($font)); } public function testTypographicalSize(): void { $font = new Font($this->getTestResourcePath('test.ttf')); $processor = Mockery::mock(AbstractFontProcessor::class)->makePartial(); $processor->shouldReceive('boxSize')->with('Hy', $font)->andReturn(new Rectangle(24, 6)); $this->assertEquals(6, $processor->typographicalSize($font)); } public function testCapHeight(): void { $font = new Font($this->getTestResourcePath('test.ttf')); $processor = Mockery::mock(AbstractFontProcessor::class)->makePartial(); $processor->shouldReceive('boxSize')->with('T', $font)->andReturn(new Rectangle(24, 6)); $this->assertEquals(6, $processor->capHeight($font)); } public function testLeading(): void { $font = (new Font($this->getTestResourcePath('test.ttf')))->setLineHeight(3); $processor = Mockery::mock(AbstractFontProcessor::class)->makePartial(); $processor->shouldReceive('boxSize')->with('Hy', $font)->andReturn(new Rectangle(24, 6)); $this->assertEquals(18, $processor->leading($font)); } } ================================================ FILE: tests/Unit/Drivers/Gd/Analyzers/ColorspaceAnalyzerTest.php ================================================ readTestImage('tile.png'); $analyzer = new ColorspaceAnalyzer(); $analyzer->setDriver(new Driver()); $result = $analyzer->analyze($image); $this->assertInstanceOf(ColorspaceInterface::class, $result); } } ================================================ FILE: tests/Unit/Drivers/Gd/Analyzers/HeightAnalyzerTest.php ================================================ readTestImage('tile.png'); $analyzer = new HeightAnalyzer(); $analyzer->setDriver(new Driver()); $result = $analyzer->analyze($image); $this->assertEquals(16, $result); } } ================================================ FILE: tests/Unit/Drivers/Gd/Analyzers/PixelColorAnalyzerTest.php ================================================ readTestImage('tile.png'); $analyzer = new PixelColorAnalyzer(0, 0); $analyzer->setDriver(new Driver()); $result = $analyzer->analyze($image); $this->assertInstanceOf(ColorInterface::class, $result); $this->assertEquals('b4e000', $result->toHex()); } } ================================================ FILE: tests/Unit/Drivers/Gd/Analyzers/PixelColorsAnalyzerTest.php ================================================ readTestImage('animation.gif'); $analyzer = new PixelColorsAnalyzer(0, 0); $analyzer->setDriver(new Driver()); $result = $analyzer->analyze($image); $this->assertInstanceOf(Collection::class, $result); $colors = array_map(fn(ColorInterface $color) => $color->toHex(), $result->toArray()); $this->assertEquals($colors, ["394b63", "394b63", "394b63", "ffa601", "ffa601", "ffa601", "ffa601", "394b63"]); } public function testAnalyzeNonAnimated(): void { $image = $this->readTestImage('tile.png'); $analyzer = new PixelColorsAnalyzer(0, 0); $analyzer->setDriver(new Driver()); $result = $analyzer->analyze($image); $this->assertInstanceOf(Collection::class, $result); $this->assertInstanceOf(ColorInterface::class, $result->first()); $this->assertEquals('b4e000', $result->first()->toHex()); } } ================================================ FILE: tests/Unit/Drivers/Gd/Analyzers/ResolutionAnalyzerTest.php ================================================ readTestImage('300dpi.png'); $analyzer = new ResolutionAnalyzer(); $analyzer->setDriver(new Driver()); $result = $analyzer->analyze($image); $this->assertInstanceOf(Resolution::class, $result); $this->assertEquals(300, $result->perInch()->x()); $this->assertEquals(300, $result->perInch()->y()); } } ================================================ FILE: tests/Unit/Drivers/Gd/Analyzers/WidthAnalyzerTest.php ================================================ readTestImage('tile.png'); $analyzer = new WidthAnalyzer(); $analyzer->setDriver(new Driver()); $result = $analyzer->analyze($image); $this->assertEquals(16, $result); } } ================================================ FILE: tests/Unit/Drivers/Gd/ClonerTest.php ================================================ getTestResourcePath('gradient.gif')); $clone = Cloner::clone($gd); $this->assertEquals(16, imagesx($gd)); $this->assertEquals(16, imagesy($gd)); $this->assertEquals(16, imagesx($clone)); $this->assertEquals(16, imagesy($clone)); $this->assertEquals( imagecolorsforindex($gd, imagecolorat($gd, 10, 10)), imagecolorsforindex($clone, imagecolorat($clone, 10, 10)) ); } public function testCloneEmpty(): void { $gd = imagecreatefromgif($this->getTestResourcePath('gradient.gif')); $clone = Cloner::cloneEmpty($gd, new Rectangle(12, 12), new Color(255, 0, 0, 0)); $this->assertEquals(16, imagesx($gd)); $this->assertEquals(16, imagesy($gd)); $this->assertEquals(12, imagesx($clone)); $this->assertEquals(12, imagesy($clone)); $this->assertEquals( ['red' => 0, 'green' => 255, 'blue' => 2, 'alpha' => 0], imagecolorsforindex($gd, imagecolorat($gd, 10, 10)), ); $this->assertEquals( ['red' => 255, 'green' => 0, 'blue' => 0, 'alpha' => 127], imagecolorsforindex($clone, imagecolorat($clone, 10, 10)) ); } public function testCloneBlended(): void { $gd = imagecreatefromgif($this->getTestResourcePath('gradient.gif')); $clone = Cloner::cloneBlended($gd, new Color(255, 0, 255, 255)); $this->assertEquals(16, imagesx($gd)); $this->assertEquals(16, imagesy($gd)); $this->assertEquals(16, imagesx($clone)); $this->assertEquals(16, imagesy($clone)); $this->assertEquals( ['red' => 0, 'green' => 0, 'blue' => 0, 'alpha' => 127], imagecolorsforindex($gd, imagecolorat($gd, 1, 0)), ); $this->assertEquals( ['red' => 255, 'green' => 0, 'blue' => 255, 'alpha' => 0], imagecolorsforindex($clone, imagecolorat($clone, 1, 0)) ); } } ================================================ FILE: tests/Unit/Drivers/Gd/ColorProcessorTest.php ================================================ colorToNative(new Color(255, 55, 0, 255)); $this->assertEquals(16725760, $result); } public function testNativeToColorInteger(): void { $processor = new ColorProcessor(); $result = $processor->nativeToColor(16725760); $this->assertInstanceOf(Color::class, $result); $this->assertEquals(255, $result->channel(Red::class)->value()); $this->assertEquals(55, $result->channel(Green::class)->value()); $this->assertEquals(0, $result->channel(Blue::class)->value()); $this->assertEquals(255, $result->channel(Alpha::class)->value()); } public function testNativeToColorArray(): void { $processor = new ColorProcessor(); $result = $processor->nativeToColor(['red' => 255, 'green' => 55, 'blue' => 0, 'alpha' => 0]); $this->assertInstanceOf(Color::class, $result); $this->assertEquals(255, $result->channel(Red::class)->value()); $this->assertEquals(55, $result->channel(Green::class)->value()); $this->assertEquals(0, $result->channel(Blue::class)->value()); $this->assertEquals(255, $result->channel(Alpha::class)->value()); } public function testNativeToColorInvalid(): void { $processor = new ColorProcessor(); $this->expectException(ColorException::class); $processor->nativeToColor('test'); } } ================================================ FILE: tests/Unit/Drivers/Gd/CoreTest.php ================================================ core = new Core([ new Frame(imagecreatetruecolor(3, 2)), new Frame(imagecreatetruecolor(3, 2)), new Frame(imagecreatetruecolor(3, 2)), ]); } public function getTestFrame(): Frame { return new Frame(imagecreatetruecolor(3, 2)); } public function testAdd(): void { $this->assertEquals(3, $this->core->count()); $result = $this->core->add($this->getTestFrame()); $this->assertEquals(4, $this->core->count()); $this->assertInstanceOf(Core::class, $result); } public function testSetNative(): void { $gd1 = imagecreatetruecolor(3, 2); $gd2 = imagecreatetruecolor(3, 2); $core = new Core([new Frame($gd1)]); $this->assertEquals($gd1, $core->native()); $core->setNative($gd2); $this->assertEquals($gd2, $core->native()); } public function testCount(): void { $this->assertEquals(3, $this->core->count()); } public function testIterator(): void { foreach ($this->core as $frame) { $this->assertInstanceOf(Frame::class, $frame); } } public function testNative(): void { $this->assertInstanceOf(GdImage::class, $this->core->native()); } public function testFrame(): void { $this->assertInstanceOf(Frame::class, $this->core->frame(0)); $this->assertInstanceOf(Frame::class, $this->core->frame(1)); $this->assertInstanceOf(Frame::class, $this->core->frame(2)); $this->expectException(AnimationException::class); $this->core->frame(3); } public function testSetGetLoops(): void { $this->assertEquals(0, $this->core->loops()); $result = $this->core->setLoops(12); $this->assertInstanceOf(Core::class, $result); $this->assertEquals(12, $this->core->loops()); } public function testHas(): void { $this->assertTrue($this->core->has(0)); $this->assertTrue($this->core->has(1)); $this->assertTrue($this->core->has(2)); $this->assertFalse($this->core->has(3)); } public function testPush(): void { $this->assertEquals(3, $this->core->count()); $result = $this->core->push($this->getTestFrame()); $this->assertEquals(4, $this->core->count()); $this->assertEquals(4, $result->count()); } public function testGet(): void { $this->assertInstanceOf(Frame::class, $this->core->get(0)); $this->assertInstanceOf(Frame::class, $this->core->get(1)); $this->assertInstanceOf(Frame::class, $this->core->get(2)); $this->assertNull($this->core->get(3)); $this->assertEquals('foo', $this->core->get(3, 'foo')); } public function testEmpty(): void { $result = $this->core->empty(); $this->assertEquals(0, $this->core->count()); $this->assertEquals(0, $result->count()); } public function testSlice(): void { $this->assertEquals(3, $this->core->count()); $result = $this->core->slice(1, 2); $this->assertEquals(2, $this->core->count()); $this->assertEquals(2, $result->count()); } public function testFirst(): void { $this->assertInstanceOf(Frame::class, $this->core->first()); } public function testLast(): void { $this->assertInstanceOf(Frame::class, $this->core->last()); } } ================================================ FILE: tests/Unit/Drivers/Gd/Decoders/AbstractDecoderTest.php ================================================ makePartial(); $this->assertEquals( MediaType::IMAGE_JPEG, $decoder->getMediaTypeByFilePath($this->getTestResourcePath('test.jpg')) ); } public function testGetMediaTypeFromFileBinary(): void { $decoder = Mockery::mock(AbstractDecoder::class)->makePartial(); $this->assertEquals( MediaType::IMAGE_JPEG, $decoder->getMediaTypeByBinary($this->getTestResourceData('test.jpg')), ); } } ================================================ FILE: tests/Unit/Drivers/Gd/Decoders/Base64ImageDecoderTest.php ================================================ decoder = new Base64ImageDecoder(); $this->decoder->setDriver(new Driver()); } public function testDecode(): void { $result = $this->decoder->decode( base64_encode($this->getTestResourceData('blue.gif')) ); $this->assertInstanceOf(Image::class, $result); } public function testDecoderInvalid(): void { $this->expectException(DecoderException::class); $this->decoder->decode('test'); } } ================================================ FILE: tests/Unit/Drivers/Gd/Decoders/BinaryImageDecoderTest.php ================================================ decoder = new BinaryImageDecoder(); $this->decoder->setDriver(new Driver()); } public function testDecodePng(): void { $image = $this->decoder->decode(file_get_contents($this->getTestResourcePath('tile.png'))); $this->assertInstanceOf(Image::class, $image); $this->assertEquals(16, $image->width()); $this->assertEquals(16, $image->height()); $this->assertCount(1, $image); } public function testDecodeGif(): void { $image = $this->decoder->decode(file_get_contents($this->getTestResourcePath('red.gif'))); $this->assertInstanceOf(Image::class, $image); $this->assertEquals(16, $image->width()); $this->assertEquals(16, $image->height()); $this->assertCount(1, $image); } public function testDecodeAnimatedGif(): void { $image = $this->decoder->decode(file_get_contents($this->getTestResourcePath('cats.gif'))); $this->assertInstanceOf(Image::class, $image); $this->assertEquals(75, $image->width()); $this->assertEquals(50, $image->height()); $this->assertCount(4, $image); } public function testDecodeJpegWithExif(): void { $image = $this->decoder->decode(file_get_contents($this->getTestResourcePath('exif.jpg'))); $this->assertInstanceOf(Image::class, $image); $this->assertEquals(16, $image->width()); $this->assertEquals(16, $image->height()); $this->assertCount(1, $image); $this->assertEquals('Oliver Vogel', $image->exif('IFD0.Artist')); } } ================================================ FILE: tests/Unit/Drivers/Gd/Decoders/DataUriImageDecoderTest.php ================================================ decoder = new DataUriImageDecoder(); $this->decoder->setDriver(new Driver()); } public function testDecode(): void { $result = $this->decoder->decode( sprintf('data:image/jpeg;base64,%s', base64_encode($this->getTestResourceData('blue.gif'))) ); $this->assertInstanceOf(Image::class, $result); } public function testDecoderNonString(): void { $this->expectException(DecoderException::class); $this->decoder->decode(new stdClass()); } public function testDecoderInvalid(): void { $this->expectException(DecoderException::class); $this->decoder->decode('invalid'); } public function testDecoderNonImage(): void { $this->expectException(DecoderException::class); $this->decoder->decode('data:text/plain;charset=utf-8,test'); } } ================================================ FILE: tests/Unit/Drivers/Gd/Decoders/EncodedImageObjectDecoderTest.php ================================================ decoder = new EncodedImageObjectDecoder(); $this->decoder->setDriver(new Driver()); } public function testDecode(): void { $result = $this->decoder->decode(new EncodedImage($this->getTestResourceData())); $this->assertInstanceOf(Image::class, $result); } } ================================================ FILE: tests/Unit/Drivers/Gd/Decoders/FilePathImageDecoderTest.php ================================================ decoder = new FilePathImageDecoder(); $this->decoder->setDriver(new Driver()); } #[DataProvider('validFormatPathsProvider')] public function testDecode(string $path, bool $result): void { if ($result === false) { $this->expectException(DecoderException::class); } $result = $this->decoder->decode($path); if ($result === true) { $this->assertInstanceOf(Image::class, $result); } } public static function validFormatPathsProvider(): Generator { yield [self::getTestResourcePath('cats.gif'), true]; yield [self::getTestResourcePath('animation.gif'), true]; yield [self::getTestResourcePath('red.gif'), true]; yield [self::getTestResourcePath('green.gif'), true]; yield [self::getTestResourcePath('blue.gif'), true]; yield [self::getTestResourcePath('gradient.bmp'), true]; yield [self::getTestResourcePath('circle.png'), true]; yield ['no-path', false]; yield [str_repeat('x', PHP_MAXPATHLEN + 1), false]; } } ================================================ FILE: tests/Unit/Drivers/Gd/Decoders/FilePointerImageDecoderTest.php ================================================ setDriver(new Driver()); $fp = fopen($this->getTestResourcePath('test.jpg'), 'r'); $result = $decoder->decode($fp); $this->assertInstanceOf(Image::class, $result); } } ================================================ FILE: tests/Unit/Drivers/Gd/Decoders/ImageObjectDecoderTest.php ================================================ decode($this->readTestImage('blue.gif')); $this->assertInstanceOf(Image::class, $result); } } ================================================ FILE: tests/Unit/Drivers/Gd/Decoders/NativeObjectDecoderTest.php ================================================ decoder = new NativeObjectDecoder(); $this->decoder->setDriver(new Driver()); } public function testDecode(): void { $result = $this->decoder->decode( imagecreatetruecolor(3, 2) ); $this->assertInstanceOf(Image::class, $result); } } ================================================ FILE: tests/Unit/Drivers/Gd/Decoders/SplFileInfoImageDecoderTest.php ================================================ setDriver(new Driver()); $result = $decoder->decode( new SplFileInfo($this->getTestResourcePath('blue.gif')) ); $this->assertInstanceOf(Image::class, $result); } } ================================================ FILE: tests/Unit/Drivers/Gd/DriverTest.php ================================================ driver = new Driver(); } public function testId(): void { $this->assertEquals('GD', $this->driver->id()); } public function testCreateImage(): void { $image = $this->driver->createImage(3, 2); $this->assertInstanceOf(ImageInterface::class, $image); $this->assertEquals(3, $image->width()); $this->assertEquals(2, $image->height()); } public function testCreateAnimation(): void { $image = $this->driver->createAnimation(function ($animation): void { $animation->add($this->getTestResourcePath('red.gif'), .25); $animation->add($this->getTestResourcePath('green.gif'), .25); })->setLoops(5); $this->assertInstanceOf(ImageInterface::class, $image); $this->assertEquals(16, $image->width()); $this->assertEquals(16, $image->height()); $this->assertEquals(5, $image->loops()); $this->assertEquals(2, $image->count()); } public function testHandleInputImage(): void { $result = $this->driver->handleInput($this->getTestResourcePath('test.jpg')); $this->assertInstanceOf(ImageInterface::class, $result); } public function testHandleInputColor(): void { $result = $this->driver->handleInput('ffffff'); $this->assertInstanceOf(ColorInterface::class, $result); } public function testHandleInputObjects(): void { $result = $this->driver->handleInput('ffffff', [ new HexColorDecoder() ]); $this->assertInstanceOf(ColorInterface::class, $result); } public function testHandleInputClassnames(): void { $result = $this->driver->handleInput('ffffff', [ HexColorDecoder::class ]); $this->assertInstanceOf(ColorInterface::class, $result); } public function testColorProcessor(): void { $result = $this->driver->colorProcessor(new Colorspace()); $this->assertInstanceOf(ColorProcessorInterface::class, $result); } #[DataProvider('supportsDataProvider')] public function testSupports(bool $result, mixed $identifier): void { $this->assertEquals($result, $this->driver->supports($identifier)); } public static function supportsDataProvider(): Generator { yield [true, Format::JPEG]; yield [true, MediaType::IMAGE_JPEG]; yield [true, MediaType::IMAGE_JPG]; yield [true, FileExtension::JPG]; yield [true, FileExtension::JPEG]; yield [true, 'jpg']; yield [true, 'jpeg']; yield [true, 'image/jpg']; yield [true, 'image/jpeg']; yield [true, Format::WEBP]; yield [true, MediaType::IMAGE_WEBP]; yield [true, MediaType::IMAGE_X_WEBP]; yield [true, FileExtension::WEBP]; yield [true, 'webp']; yield [true, 'image/webp']; yield [true, 'image/x-webp']; yield [true, Format::GIF]; yield [true, MediaType::IMAGE_GIF]; yield [true, FileExtension::GIF]; yield [true, 'gif']; yield [true, 'image/gif']; yield [true, Format::PNG]; yield [true, MediaType::IMAGE_PNG]; yield [true, MediaType::IMAGE_X_PNG]; yield [true, FileExtension::PNG]; yield [true, 'png']; yield [true, 'image/png']; yield [true, 'image/x-png']; yield [true, Format::AVIF]; yield [true, MediaType::IMAGE_AVIF]; yield [true, MediaType::IMAGE_X_AVIF]; yield [true, FileExtension::AVIF]; yield [true, 'avif']; yield [true, 'image/avif']; yield [true, 'image/x-avif']; yield [true, Format::BMP]; yield [true, FileExtension::BMP]; yield [true, MediaType::IMAGE_BMP]; yield [true, MediaType::IMAGE_MS_BMP]; yield [true, MediaType::IMAGE_X_BITMAP]; yield [true, MediaType::IMAGE_X_BMP]; yield [true, MediaType::IMAGE_X_MS_BMP]; yield [true, MediaType::IMAGE_X_WINDOWS_BMP]; yield [true, MediaType::IMAGE_X_WIN_BITMAP]; yield [true, MediaType::IMAGE_X_XBITMAP]; yield [true, 'bmp']; yield [true, 'image/bmp']; yield [true, 'image/ms-bmp']; yield [true, 'image/x-bitmap']; yield [true, 'image/x-bmp']; yield [true, 'image/x-ms-bmp']; yield [true, 'image/x-windows-bmp']; yield [true, 'image/x-win-bitmap']; yield [true, 'image/x-xbitmap']; yield [false, Format::TIFF]; yield [false, MediaType::IMAGE_TIFF]; yield [false, FileExtension::TIFF]; yield [false, FileExtension::TIF]; yield [false, 'tif']; yield [false, 'tiff']; yield [false, 'image/tiff']; yield [false, Format::JP2]; yield [false, MediaType::IMAGE_JP2]; yield [false, MediaType::IMAGE_JPX]; yield [false, MediaType::IMAGE_JPM]; yield [false, FileExtension::TIFF]; yield [false, FileExtension::TIF]; yield [false, FileExtension::JP2]; yield [false, FileExtension::J2K]; yield [false, FileExtension::JPF]; yield [false, FileExtension::JPM]; yield [false, FileExtension::JPG2]; yield [false, FileExtension::J2C]; yield [false, FileExtension::JPC]; yield [false, FileExtension::JPX]; yield [false, 'jp2']; yield [false, 'j2k']; yield [false, 'jpf']; yield [false, 'jpm']; yield [false, 'jpg2']; yield [false, 'j2c']; yield [false, 'jpc']; yield [false, 'jpx']; yield [false, Format::HEIC]; yield [false, MediaType::IMAGE_HEIC]; yield [false, MediaType::IMAGE_HEIF]; yield [false, FileExtension::HEIC]; yield [false, FileExtension::HEIF]; yield [false, 'heic']; yield [false, 'heif']; yield [false, 'image/heic']; yield [false, 'image/heif']; yield [false, 'tga']; yield [false, 'image/tga']; yield [false, 'image/x-targa']; yield [false, 'foo']; yield [false, '']; } public function testVersion(): void { $this->assertTrue(is_string($this->driver->version())); } #[DataProvider('spezializeDataProvider')] public function testSpecialize(string $inputClassname, string $outputClassname): void { $this->assertInstanceOf($outputClassname, $this->driver->specialize(new $inputClassname())); } public static function spezializeDataProvider(): Generator { // specializing possible yield [GenericResizeModifier::class, ResizeModifier::class]; yield [GenericWidthAnalyzer::class, WidthAnalyzer::class]; yield [GenericPngEncoder::class, PngEncoder::class]; yield [GenericFilePathImageDecoder::class, FilePathImageDecoder::class]; // already specialized yield [ResizeModifier::class, ResizeModifier::class]; yield [WidthAnalyzer::class, WidthAnalyzer::class]; yield [PngEncoder::class, PngEncoder::class]; yield [FilePathImageDecoder::class, FilePathImageDecoder::class]; } public function testSpecializeFailure(): void { $this->expectException(NotSupportedException::class); $this->driver->specialize(new class () implements AnalyzerInterface, SpecializableInterface { protected DriverInterface $driver; public function analyze(ImageInterface $image): mixed { return true; } /** @return array **/ public function specializable(): array { return []; } public function setDriver(DriverInterface $driver): SpecializableInterface { return $this; } public function driver(): DriverInterface { return $this->driver; } }); } } ================================================ FILE: tests/Unit/Drivers/Gd/Encoders/AvifEncoderTest.php ================================================ createTestImage(3, 2); $encoder = new AvifEncoder(10); $result = $encoder->encode($image); $this->assertMediaType('image/avif', $result); $this->assertEquals('image/avif', $result->mimetype()); } } ================================================ FILE: tests/Unit/Drivers/Gd/Encoders/BmpEncoderTest.php ================================================ createTestImage(3, 2); $encoder = new BmpEncoder(); $result = $encoder->encode($image); $this->assertMediaType(['image/bmp', 'image/x-ms-bmp'], $result); $this->assertEquals('image/bmp', $result->mimetype()); } } ================================================ FILE: tests/Unit/Drivers/Gd/Encoders/GifEncoderTest.php ================================================ createTestAnimation(); $encoder = new GifEncoder(); $result = $encoder->encode($image); $this->assertMediaType('image/gif', $result); $this->assertEquals('image/gif', $result->mimetype()); $this->assertFalse( Decoder::decode((string) $result)->getFirstFrame()->getImageDescriptor()->isInterlaced() ); } public function testEncodeInterlaced(): void { $image = $this->createTestImage(3, 2); $encoder = new GifEncoder(interlaced: true); $result = $encoder->encode($image); $this->assertMediaType('image/gif', $result); $this->assertEquals('image/gif', $result->mimetype()); $this->assertTrue( Decoder::decode((string) $result)->getFirstFrame()->getImageDescriptor()->isInterlaced() ); } public function testEncodeInterlacedAnimation(): void { $image = $this->createTestAnimation(); $encoder = new GifEncoder(interlaced: true); $result = $encoder->encode($image); $this->assertMediaType('image/gif', $result); $this->assertEquals('image/gif', $result->mimetype()); $this->assertTrue( Decoder::decode((string) $result)->getFirstFrame()->getImageDescriptor()->isInterlaced() ); } } ================================================ FILE: tests/Unit/Drivers/Gd/Encoders/JpegEncoderTest.php ================================================ createTestImage(3, 2); $encoder = new JpegEncoder(75); $encoder->setDriver(new Driver()); $result = $encoder->encode($image); $this->assertMediaType('image/jpeg', $result); $this->assertEquals('image/jpeg', $result->mimetype()); } public function testEncodeProgressive(): void { $image = $this->createTestImage(3, 2); $encoder = new JpegEncoder(progressive: true); $encoder->setDriver(new Driver()); $result = $encoder->encode($image); $this->assertMediaType('image/jpeg', $result); $this->assertEquals('image/jpeg', $result->mimetype()); $this->assertTrue($this->isProgressiveJpeg($result)); } } ================================================ FILE: tests/Unit/Drivers/Gd/Encoders/PngEncoderTest.php ================================================ createTestImage(3, 2); $encoder = new PngEncoder(); $result = $encoder->encode($image); $this->assertMediaType('image/png', $result); $this->assertEquals('image/png', $result->mimetype()); $this->assertFalse($this->isInterlacedPng($result)); } public function testEncodeInterlaced(): void { $image = $this->createTestImage(3, 2); $encoder = new PngEncoder(interlaced: true); $result = $encoder->encode($image); $this->assertMediaType('image/png', $result); $this->assertEquals('image/png', $result->mimetype()); $this->assertTrue($this->isInterlacedPng($result)); } #[DataProvider('indexedDataProvider')] public function testEncoderIndexed(ImageInterface $image, PngEncoder $encoder, string $result): void { $this->assertEquals( $result, $this->pngColorType($encoder->encode($image)), ); } public static function indexedDataProvider(): Generator { yield [ static::createTestImage(3, 2), // new new PngEncoder(indexed: false), 'truecolor-alpha', ]; yield [ static::createTestImage(3, 2), // new new PngEncoder(indexed: true), 'indexed', ]; yield [ static::readTestImage('circle.png'), // truecolor-alpha new PngEncoder(indexed: false), 'truecolor-alpha', ]; yield [ static::readTestImage('circle.png'), // indexedcolor-alpha new PngEncoder(indexed: true), 'indexed', ]; yield [ static::readTestImage('tile.png'), // indexed new PngEncoder(indexed: false), 'truecolor-alpha', ]; yield [ static::readTestImage('tile.png'), // indexed new PngEncoder(indexed: true), 'indexed', ]; yield [ static::readTestImage('test.jpg'), // jpeg new PngEncoder(indexed: false), 'truecolor-alpha', ]; yield [ static::readTestImage('test.jpg'), // jpeg new PngEncoder(indexed: true), 'indexed', ]; } } ================================================ FILE: tests/Unit/Drivers/Gd/Encoders/WebpEncoderTest.php ================================================ createTestImage(3, 2); $encoder = new WebpEncoder(75); $result = $encoder->encode($image); $this->assertMediaType('image/webp', $result); $this->assertEquals('image/webp', $result->mimetype()); } } ================================================ FILE: tests/Unit/Drivers/Gd/FontProcessorTest.php ================================================ boxSize('test', new Font()); $this->assertInstanceOf(SizeInterface::class, $size); $this->assertEquals(16, $size->width()); $this->assertEquals(8, $size->height()); } public function testBoxSizeGdTwo(): void { $processor = new FontProcessor(); $size = $processor->boxSize('test', new Font('2')); $this->assertInstanceOf(SizeInterface::class, $size); $this->assertEquals(24, $size->width()); $this->assertEquals(14, $size->height()); } public function testBoxSizeGdThree(): void { $processor = new FontProcessor(); $size = $processor->boxSize('test', new Font('3')); $this->assertInstanceOf(SizeInterface::class, $size); $this->assertEquals(28, $size->width()); $this->assertEquals(14, $size->height()); } public function testBoxSizeGdFour(): void { $processor = new FontProcessor(); $size = $processor->boxSize('test', new Font('4')); $this->assertInstanceOf(SizeInterface::class, $size); $this->assertEquals(32, $size->width()); $this->assertEquals(16, $size->height()); } public function testBoxSizeGdFive(): void { $processor = new FontProcessor(); $size = $processor->boxSize('test', new Font('5')); $this->assertInstanceOf(SizeInterface::class, $size); $this->assertEquals(36, $size->width()); $this->assertEquals(16, $size->height()); } public function testBoxSizeTtf(): void { $processor = new FontProcessor(); $size = $processor->boxSize('ABC', $this->testFont()); $this->assertInstanceOf(SizeInterface::class, $size); $this->assertContains($size->width(), [74, 75, 76]); $this->assertContains($size->height(), [19, 20, 21]); } public function testNativeFontSize(): void { $processor = new FontProcessor(); $size = $processor->nativeFontSize(new Font('5')); $this->assertEquals(9.12, $size); } public function testTextBlock(): void { $processor = new FontProcessor(); $result = $processor->textBlock('test', new Font(), new Point(0, 0)); $this->assertInstanceOf(TextBlock::class, $result); } public function testTypographicalSize(): void { $processor = new FontProcessor(); $result = $processor->typographicalSize(new Font()); $this->assertEquals(8, $result); } public function testCapHeight(): void { $processor = new FontProcessor(); $result = $processor->capHeight(new Font()); $this->assertEquals(8, $result); } public function testLeading(): void { $processor = new FontProcessor(); $result = $processor->leading(new Font()); $this->assertEquals(8, $result); } public function testNativeFontSizeTtf(): void { $processor = new FontProcessor(); $size = $processor->nativeFontSize($this->testFont()); $this->assertEquals(42.56, $size); } public function testTextBlockTtf(): void { $processor = new FontProcessor(); $result = $processor->textBlock('test', $this->testFont(), new Point(0, 0)); $this->assertInstanceOf(TextBlock::class, $result); } public function testTypographicalSizeTtf(): void { $processor = new FontProcessor(); $result = $processor->typographicalSize($this->testFont()); $this->assertContains($result, [44, 45]); } public function testCapHeightTtf(): void { $processor = new FontProcessor(); $result = $processor->capHeight($this->testFont()); $this->assertContains($result, [44, 45]); } public function testLeadingTtf(): void { $processor = new FontProcessor(); $result = $processor->leading($this->testFont()); $this->assertContains($result, [44, 45]); } private function testFont(): Font { return (new Font($this->getTestResourcePath('test.ttf')))->setSize(56); } } ================================================ FILE: tests/Unit/Drivers/Gd/FrameTest.php ================================================ getTestFrame(); $this->assertInstanceOf(Frame::class, $frame); } public function testGetNative(): void { $frame = $this->getTestFrame(); $this->assertInstanceOf(GdImage::class, $frame->native()); } public function testSetCore(): void { $core1 = imagecreatetruecolor(3, 2); $core2 = imagecreatetruecolor(3, 3); $frame = new Frame($core1); $this->assertEquals(2, $frame->size()->height()); $result = $frame->setNative($core2); $this->assertInstanceOf(Frame::class, $result); $this->assertEquals(3, $frame->size()->height()); } public function testGetSize(): void { $frame = $this->getTestFrame(); $this->assertInstanceOf(Rectangle::class, $frame->size()); } public function testSetGetDelay(): void { $frame = $this->getTestFrame(); $this->assertEquals(0, $frame->delay()); $result = $frame->setDelay(1.5); $this->assertInstanceOf(Frame::class, $result); $this->assertEquals(1.5, $frame->delay()); } public function testSetGetDispose(): void { $frame = $this->getTestFrame(); $this->assertEquals(1, $frame->dispose()); $result = $frame->setDispose(3); $this->assertInstanceOf(Frame::class, $result); $this->assertEquals(3, $frame->dispose()); } public function testSetGetOffsetLeft(): void { $frame = $this->getTestFrame(); $this->assertEquals(0, $frame->offsetLeft()); $result = $frame->setOffsetLeft(100); $this->assertInstanceOf(Frame::class, $result); $this->assertEquals(100, $frame->offsetLeft()); } public function testSetGetOffsetTop(): void { $frame = $this->getTestFrame(); $this->assertEquals(0, $frame->offsetTop()); $result = $frame->setOffsetTop(100); $this->assertInstanceOf(Frame::class, $result); $this->assertEquals(100, $frame->offsetTop()); } public function testSetGetOffset(): void { $frame = $this->getTestFrame(); $this->assertEquals(0, $frame->offsetTop()); $this->assertEquals(0, $frame->offsetLeft()); $result = $frame->setOffset(100, 200); $this->assertInstanceOf(Frame::class, $result); $this->assertEquals(100, $frame->offsetLeft()); $this->assertEquals(200, $frame->offsetTop()); } public function testToImage(): void { $frame = $this->getTestFrame(); $this->assertInstanceOf(Image::class, $frame->toImage(new Driver())); } public function testDebugInfo(): void { $info = $this->getTestFrame()->__debugInfo(); $this->assertEquals(0, $info['delay']); $this->assertEquals(0, $info['left']); $this->assertEquals(0, $info['top']); $this->assertEquals(1, $info['dispose']); } } ================================================ FILE: tests/Unit/Drivers/Gd/ImageTest.php ================================================ image = new Image( new Driver(), new Core([ new Frame(imagecreatetruecolor(3, 2)), new Frame(imagecreatetruecolor(3, 2)), ]), new Collection([ 'test' => 'foo' ]), ); } public function testClone(): void { $image = $this->readTestImage('gradient.gif'); $clone = clone $image; $this->assertEquals(16, $image->width()); $this->assertEquals(16, $clone->width()); $result = $clone->crop(4, 4); $this->assertEquals(16, $image->width()); $this->assertEquals(4, $clone->width()); $this->assertEquals(4, $result->width()); $this->assertEquals('ff0000', $image->pickColor(0, 0)->toHex()); $this->assertTransparency($image->pickColor(1, 0)); $this->assertEquals('ff0000', $clone->pickColor(0, 0)->toHex()); $this->assertTransparency($image->pickColor(1, 0)); } public function testDriver(): void { $this->assertInstanceOf(Driver::class, $this->image->driver()); } public function testCore(): void { $this->assertInstanceOf(Core::class, $this->image->core()); } public function testCount(): void { $this->assertEquals(2, $this->image->count()); } public function testIteration(): void { foreach ($this->image as $frame) { $this->assertInstanceOf(Frame::class, $frame); } } public function testIsAnimated(): void { $this->assertTrue($this->image->isAnimated()); } public function testSetGetLoops(): void { $this->assertEquals(0, $this->image->loops()); $result = $this->image->setLoops(10); $this->assertInstanceOf(ImageInterface::class, $result); $this->assertEquals(10, $this->image->loops()); } public function testRemoveAnimation(): void { $this->assertTrue($this->image->isAnimated()); $result = $this->image->removeAnimation(); $this->assertInstanceOf(ImageInterface::class, $result); $this->assertFalse($this->image->isAnimated()); } public function testSliceAnimation(): void { $this->assertEquals(2, $this->image->count()); $result = $this->image->sliceAnimation(0, 1); $this->assertInstanceOf(ImageInterface::class, $result); $this->assertEquals(1, $this->image->count()); } public function testExif(): void { $this->assertInstanceOf(Collection::class, $this->image->exif()); $this->assertEquals('foo', $this->image->exif('test')); } public function testModify(): void { $result = $this->image->modify(new GreyscaleModifier()); $this->assertInstanceOf(Image::class, $result); } public function testAnalyze(): void { $result = $this->image->analyze(new WidthAnalyzer()); $this->assertEquals(3, $result); } public function testEncode(): void { $result = $this->image->encode(new PngEncoder()); $this->assertInstanceOf(EncodedImage::class, $result); } public function testAutoEncode(): void { $result = $this->readTestImage('blue.gif')->encode(); $this->assertInstanceOf(EncodedImage::class, $result); $this->assertMediaType('image/gif', $result); } public function testEncodeByMediaType(): void { $result = $this->readTestImage('blue.gif')->encodeByMediaType(); $this->assertInstanceOf(EncodedImage::class, $result); $this->assertMediaType('image/gif', $result); $result = $this->readTestImage('blue.gif')->encodeByMediaType('image/png'); $this->assertInstanceOf(EncodedImage::class, $result); $this->assertMediaType('image/png', $result); $result = $this->readTestImage('blue.gif')->encodeByMediaType(MediaType::IMAGE_PNG); $this->assertInstanceOf(EncodedImage::class, $result); $this->assertMediaType('image/png', $result); } public function testEncodeByExtension(): void { $result = $this->readTestImage('blue.gif')->encodeByExtension(); $this->assertInstanceOf(EncodedImage::class, $result); $this->assertMediaType('image/gif', $result); $result = $this->readTestImage('blue.gif')->encodeByExtension('png'); $this->assertInstanceOf(EncodedImage::class, $result); $this->assertMediaType('image/png', $result); $result = $this->readTestImage('blue.gif')->encodeByExtension(FileExtension::PNG); $this->assertInstanceOf(EncodedImage::class, $result); $this->assertMediaType('image/png', $result); } public function testEncodeByPath(): void { $result = $this->readTestImage('blue.gif')->encodeByPath(); $this->assertInstanceOf(EncodedImage::class, $result); $this->assertMediaType('image/gif', $result); $result = $this->readTestImage('blue.gif')->encodeByPath('foo/bar.png'); $this->assertInstanceOf(EncodedImage::class, $result); $this->assertMediaType('image/png', $result); } public function testSaveAsFormat(): void { $path = __DIR__ . '/tmp.png'; $result = $this->readTestImage('blue.gif')->save($path); $this->assertInstanceOf(Image::class, $result); $this->assertFileExists($path); $this->assertMediaType('image/png', file_get_contents($path)); unlink($path); } public function testSaveFallback(): void { $path = __DIR__ . '/tmp.unknown'; $result = $this->readTestImage('blue.gif')->save($path); $this->assertInstanceOf(Image::class, $result); $this->assertFileExists($path); $this->assertMediaType('image/gif', file_get_contents($path)); unlink($path); } public function testSaveUndeterminedPath(): void { $this->expectException(EncoderException::class); $this->createTestImage(2, 3)->save(); } public function testWidthHeightSize(): void { $this->assertEquals(3, $this->image->width()); $this->assertEquals(2, $this->image->height()); $this->assertInstanceOf(SizeInterface::class, $this->image->size()); } public function testColorspace(): void { $this->assertInstanceOf(ColorspaceInterface::class, $this->image->colorspace()); } public function testSetColorspace(): void { $this->expectException(NotSupportedException::class); $this->image->setColorspace(Colorspace::class); } public function testSetGetResolution(): void { $resolution = $this->image->resolution(); $this->assertInstanceOf(ResolutionInterface::class, $resolution); $this->assertEquals(96, $resolution->x()); $this->assertEquals(96, $resolution->y()); $result = $this->image->setResolution(300, 300); $resolution = $this->image->resolution(); $this->assertInstanceOf(ImageInterface::class, $result); $this->assertEquals(300, $resolution->x()); $this->assertEquals(300, $resolution->y()); } public function testPickColor(): void { $this->assertInstanceOf(ColorInterface::class, $this->image->pickColor(0, 0)); $this->assertInstanceOf(ColorInterface::class, $this->image->pickColor(0, 0, 1)); } public function testPickColors(): void { $result = $this->image->pickColors(0, 0); $this->assertInstanceOf(Collection::class, $result); $this->assertEquals(2, $result->count()); } public function testProfile(): void { $this->expectException(NotSupportedException::class); $this->image->profile(); } public function testReduceColors(): void { $image = $this->readTestImage(); $result = $image->reduceColors(8); $this->assertInstanceOf(ImageInterface::class, $result); } public function testSharpen(): void { $this->assertInstanceOf(Image::class, $this->image->sharpen(12)); } public function testText(): void { $this->assertInstanceOf(Image::class, $this->image->text('test', 0, 0, new Font())); } public function testBlendTransparencyDefault(): void { $image = $this->readTestImage('gradient.gif'); $this->assertColor(0, 0, 0, 0, $image->pickColor(1, 0)); $result = $image->blendTransparency(); $this->assertColor(255, 255, 255, 255, $image->pickColor(1, 0)); $this->assertColor(255, 255, 255, 255, $result->pickColor(1, 0)); } public function testBlendTransparencyArgument(): void { $image = $this->readTestImage('gradient.gif'); $this->assertColor(0, 0, 0, 0, $image->pickColor(1, 0)); $result = $image->blendTransparency('ff5500'); $this->assertColor(255, 85, 0, 255, $image->pickColor(1, 0)); $this->assertColor(255, 85, 0, 255, $result->pickColor(1, 0)); } public function testBlendTransparencyIgnoreTransparencyInBlendingColor(): void { $image = $this->readTestImage('gradient.gif'); $this->assertColor(0, 0, 0, 0, $image->pickColor(1, 0)); $result = $image->blendTransparency('ff550055'); $this->assertColor(255, 85, 0, 255, $image->pickColor(1, 0)); $this->assertColor(255, 85, 0, 255, $result->pickColor(1, 0)); } public function testToJpeg(): void { $this->assertMediaType('image/jpeg', $this->image->toJpeg()); $this->assertMediaType('image/jpeg', $this->image->toJpg()); } public function testToJpeg2000(): void { $this->expectException(NotSupportedException::class); $this->image->toJpeg2000(); } public function testToPng(): void { $this->assertMediaType('image/png', $this->image->toPng()); } public function testToGif(): void { $this->assertMediaType('image/gif', $this->image->toGif()); } public function testToWebp(): void { $this->assertMediaType('image/webp', $this->image->toWebp()); } public function testToBitmap(): void { $this->assertMediaTypeBitmap($this->image->toBitmap()); $this->assertMediaTypeBitmap($this->image->toBmp()); } public function testToAvif(): void { $this->assertMediaType('image/avif', $this->image->toAvif()); } public function testToTiff(): void { $this->expectException(NotSupportedException::class); $this->image->toTiff(); } public function testToHeic(): void { $this->expectException(NotSupportedException::class); $this->image->toHeic(); } public function testInvert(): void { $image = $this->readTestImage('trim.png'); $this->assertEquals('00aef0', $image->pickColor(0, 0)->toHex()); $this->assertEquals('ffa601', $image->pickColor(25, 25)->toHex()); $result = $image->invert(); $this->assertInstanceOf(ImageInterface::class, $result); $this->assertEquals('ff510f', $image->pickColor(0, 0)->toHex()); $this->assertEquals('0059fe', $image->pickColor(25, 25)->toHex()); } public function testPixelate(): void { $image = $this->readTestImage('trim.png'); $this->assertEquals('00aef0', $image->pickColor(0, 0)->toHex()); $this->assertEquals('00aef0', $image->pickColor(14, 14)->toHex()); $result = $image->pixelate(10); $this->assertInstanceOf(ImageInterface::class, $result); $this->assertEquals('00aef0', $image->pickColor(0, 0)->toHex()); $this->assertEquals('6aaa8b', $image->pickColor(14, 14)->toHex()); } public function testGreyscale(): void { $image = $this->readTestImage('trim.png'); $this->assertFalse($image->pickColor(0, 0)->isGreyscale()); $result = $image->greyscale(); $this->assertInstanceOf(ImageInterface::class, $result); $this->assertTrue($image->pickColor(0, 0)->isGreyscale()); } public function testBrightness(): void { $image = $this->readTestImage('trim.png'); $this->assertEquals('00aef0', $image->pickColor(14, 14)->toHex()); $result = $image->brightness(30); $this->assertInstanceOf(ImageInterface::class, $result); $this->assertEquals('4cfaff', $image->pickColor(14, 14)->toHex()); } public function testDebugInfo(): void { $info = $this->readTestImage('trim.png')->__debugInfo(); $this->assertArrayHasKey('width', $info); $this->assertArrayHasKey('height', $info); } } ================================================ FILE: tests/Unit/Drivers/Gd/Modifiers/BlurModifierTest.php ================================================ readTestImage('trim.png'); $this->assertEquals('00aef0', $image->pickColor(14, 14)->toHex()); $image->modify(new BlurModifier(30)); $this->assertEquals('4fa68d', $image->pickColor(14, 14)->toHex()); } } ================================================ FILE: tests/Unit/Drivers/Gd/Modifiers/BrightnessModifierTest.php ================================================ readTestImage('trim.png'); $this->assertEquals('00aef0', $image->pickColor(14, 14)->toHex()); $image->modify(new BrightnessModifier(30)); $this->assertEquals('4cfaff', $image->pickColor(14, 14)->toHex()); } } ================================================ FILE: tests/Unit/Drivers/Gd/Modifiers/ColorizeModifierTest.php ================================================ readTestImage('tile.png'); $image = $image->modify(new ColorizeModifier(100, -100, -100)); $this->assertColor(255, 0, 0, 255, $image->pickColor(5, 5)); $this->assertColor(255, 0, 0, 255, $image->pickColor(15, 15)); } } ================================================ FILE: tests/Unit/Drivers/Gd/Modifiers/ContainModifierTest.php ================================================ readTestImage('blocks.png'); $this->assertEquals(640, $image->width()); $this->assertEquals(480, $image->height()); $image->modify(new ContainModifier(200, 100, 'ff0')); $this->assertEquals(200, $image->width()); $this->assertEquals(100, $image->height()); $this->assertColor(255, 255, 0, 255, $image->pickColor(0, 0)); $this->assertTransparency($image->pickColor(140, 10)); $this->assertColor(255, 255, 0, 255, $image->pickColor(175, 10)); } } ================================================ FILE: tests/Unit/Drivers/Gd/Modifiers/ContrastModifierTest.php ================================================ readTestImage('trim.png'); $this->assertEquals('00aef0', $image->pickColor(14, 14)->toHex()); $image->modify(new ContrastModifier(30)); $this->assertEquals('00ceff', $image->pickColor(14, 14)->toHex()); } } ================================================ FILE: tests/Unit/Drivers/Gd/Modifiers/CoverDownModifierTest.php ================================================ readTestImage('blocks.png'); $this->assertEquals(640, $image->width()); $this->assertEquals(480, $image->height()); $image->modify(new CoverDownModifier(100, 100, 'center')); $this->assertEquals(100, $image->width()); $this->assertEquals(100, $image->height()); $this->assertColor(255, 0, 0, 255, $image->pickColor(90, 90)); $this->assertColor(0, 255, 0, 255, $image->pickColor(65, 70)); $this->assertColor(0, 0, 255, 255, $image->pickColor(70, 52)); $this->assertTransparency($image->pickColor(90, 30)); } public function testModifyOddSize(): void { $image = $this->createTestImage(375, 250); $image->modify(new CoverDownModifier(240, 90, 'center')); $this->assertEquals(240, $image->width()); $this->assertEquals(90, $image->height()); } } ================================================ FILE: tests/Unit/Drivers/Gd/Modifiers/CoverModifierTest.php ================================================ readTestImage('blocks.png'); $this->assertEquals(640, $image->width()); $this->assertEquals(480, $image->height()); $image->modify(new CoverModifier(100, 100, 'center')); $this->assertEquals(100, $image->width()); $this->assertEquals(100, $image->height()); $this->assertColor(255, 0, 0, 255, $image->pickColor(90, 90)); $this->assertColor(0, 255, 0, 255, $image->pickColor(65, 70)); $this->assertColor(0, 0, 255, 255, $image->pickColor(70, 52)); $this->assertTransparency($image->pickColor(90, 30)); } public function testModifyOddSize(): void { $image = $this->createTestImage(375, 250); $image->modify(new CoverModifier(240, 90, 'center')); $this->assertEquals(240, $image->width()); $this->assertEquals(90, $image->height()); } } ================================================ FILE: tests/Unit/Drivers/Gd/Modifiers/CropModifierTest.php ================================================ readTestImage('blocks.png'); $image = $image->modify(new CropModifier(200, 200, 0, 0, 'ffffff', 'bottom-right')); $this->assertEquals(200, $image->width()); $this->assertEquals(200, $image->height()); $this->assertColor(255, 0, 0, 255, $image->pickColor(5, 5)); $this->assertColor(255, 0, 0, 255, $image->pickColor(100, 100)); $this->assertColor(255, 0, 0, 255, $image->pickColor(190, 190)); } public function testModifyExtend(): void { $image = $this->readTestImage('blocks.png'); $image = $image->modify(new CropModifier(800, 100, -10, -10, 'ff0000', 'top-left')); $this->assertEquals(800, $image->width()); $this->assertEquals(100, $image->height()); $this->assertColor(255, 0, 0, 255, $image->pickColor(9, 9)); $this->assertColor(0, 0, 255, 255, $image->pickColor(16, 16)); $this->assertColor(0, 0, 255, 255, $image->pickColor(445, 16)); $this->assertTransparency($image->pickColor(460, 16)); } public function testModifySinglePixel(): void { $image = $this->createTestImage(1, 1); $this->assertEquals(1, $image->width()); $this->assertEquals(1, $image->height()); $image->modify(new CropModifier(3, 3, 0, 0, 'ff0', 'center')); $this->assertEquals(3, $image->width()); $this->assertEquals(3, $image->height()); $this->assertColor(255, 255, 0, 255, $image->pickColor(0, 0)); $this->assertColor(255, 0, 0, 255, $image->pickColor(1, 1)); $this->assertColor(255, 255, 0, 255, $image->pickColor(2, 2)); } public function testModifyKeepsResolution(): void { $image = $this->readTestImage('300dpi.png'); $this->assertEquals(300, round($image->resolution()->perInch()->x())); $image = $image->modify(new CropModifier(800, 100, -10, -10, 'ff0000')); $this->assertEquals(300, round($image->resolution()->perInch()->x())); } public function testHalfTransparent(): void { $image = $this->createTestImage(16, 16); $image->modify(new CropModifier(32, 32, 0, 0, '00f5', 'center')); $this->assertEquals(32, $image->width()); $this->assertEquals(32, $image->height()); $this->assertColor(0, 0, 255, 85, $image->pickColor(5, 5)); $this->assertColor(0, 0, 255, 85, $image->pickColor(16, 5)); $this->assertColor(0, 0, 255, 85, $image->pickColor(30, 5)); $this->assertColor(0, 0, 255, 85, $image->pickColor(5, 16)); $this->assertColor(255, 0, 0, 255, $image->pickColor(16, 16)); $this->assertColor(0, 0, 255, 85, $image->pickColor(30, 16)); $this->assertColor(0, 0, 255, 85, $image->pickColor(5, 30)); $this->assertColor(0, 0, 255, 85, $image->pickColor(16, 30)); $this->assertColor(0, 0, 255, 85, $image->pickColor(30, 30)); } public function testMergeTransparentBackgrounds(): void { $image = $this->createTestImage(1, 1)->fill('f00'); $this->assertEquals(1, $image->width()); $this->assertEquals(1, $image->height()); $image->modify(new CropModifier(3, 3, 0, 0, '00f7', 'center')); $this->assertEquals(3, $image->width()); $this->assertEquals(3, $image->height()); $this->assertColor(0, 0, 255, 119, $image->pickColor(0, 0)); $this->assertColor(255, 0, 0, 255, $image->pickColor(1, 1)); $this->assertColor(0, 0, 255, 119, $image->pickColor(2, 2)); } } ================================================ FILE: tests/Unit/Drivers/Gd/Modifiers/DrawBezierModifierTest.php ================================================ readTestImage('trim.png'); $this->assertEquals('00aef0', $image->pickColor(14, 14)->toHex()); $drawable = new Bezier([ new Point(0, 0), new Point(15, 0), new Point(15, 15), new Point(0, 15) ]); $drawable->setBackgroundColor('b53717'); $image->modify(new DrawBezierModifier($drawable)); $this->assertEquals('b53717', $image->pickColor(5, 5)->toHex()); } } ================================================ FILE: tests/Unit/Drivers/Gd/Modifiers/DrawEllipseModifierTest.php ================================================ readTestImage('trim.png'); $this->assertEquals('00aef0', $image->pickColor(14, 14)->toHex()); $drawable = new Ellipse(10, 10, new Point(14, 14)); $drawable->setBackgroundColor('b53717'); $image->modify(new DrawEllipseModifier($drawable)); $this->assertEquals('b53717', $image->pickColor(14, 14)->toHex()); } } ================================================ FILE: tests/Unit/Drivers/Gd/Modifiers/DrawLineModifierTest.php ================================================ readTestImage('trim.png'); $this->assertEquals('00aef0', $image->pickColor(14, 14)->toHex()); $line = new Line(new Point(0, 0), new Point(10, 0), 4); $line->setBackgroundColor('b53517'); $image->modify(new DrawLineModifier($line)); $this->assertEquals('b53517', $image->pickColor(0, 0)->toHex()); } } ================================================ FILE: tests/Unit/Drivers/Gd/Modifiers/DrawPixelModifierTest.php ================================================ readTestImage('trim.png'); $this->assertEquals('00aef0', $image->pickColor(14, 14)->toHex()); $image->modify(new DrawPixelModifier(new Point(14, 14), 'ffffff')); $this->assertEquals('ffffff', $image->pickColor(14, 14)->toHex()); } } ================================================ FILE: tests/Unit/Drivers/Gd/Modifiers/DrawPolygonModifierTest.php ================================================ readTestImage('trim.png'); $this->assertEquals('00aef0', $image->pickColor(14, 14)->toHex()); $drawable = new Polygon([new Point(0, 0), new Point(15, 15), new Point(20, 20)]); $drawable->setBackgroundColor('b53717'); $image->modify(new DrawPolygonModifier($drawable)); $this->assertEquals('b53717', $image->pickColor(14, 14)->toHex()); } } ================================================ FILE: tests/Unit/Drivers/Gd/Modifiers/DrawRectangleModifierTest.php ================================================ readTestImage('trim.png'); $this->assertEquals('00aef0', $image->pickColor(14, 14)->toHex()); $rectangle = new Rectangle(300, 200, new Point(14, 14)); $rectangle->setBackgroundColor('ffffff'); $image->modify(new DrawRectangleModifier($rectangle)); $this->assertEquals('ffffff', $image->pickColor(14, 14)->toHex()); } } ================================================ FILE: tests/Unit/Drivers/Gd/Modifiers/FillModifierTest.php ================================================ readTestImage('blocks.png'); $this->assertEquals('0000ff', $image->pickColor(420, 270)->toHex()); $this->assertEquals('ff0000', $image->pickColor(540, 400)->toHex()); $image->modify(new FillModifier(new Color(204, 204, 204), new Point(540, 400))); $this->assertEquals('0000ff', $image->pickColor(420, 270)->toHex()); $this->assertEquals('cccccc', $image->pickColor(540, 400)->toHex()); } public function testFillAllColor(): void { $image = $this->readTestImage('blocks.png'); $this->assertEquals('0000ff', $image->pickColor(420, 270)->toHex()); $this->assertEquals('ff0000', $image->pickColor(540, 400)->toHex()); $image->modify(new FillModifier(new Color(204, 204, 204))); $this->assertEquals('cccccc', $image->pickColor(420, 270)->toHex()); $this->assertEquals('cccccc', $image->pickColor(540, 400)->toHex()); } } ================================================ FILE: tests/Unit/Drivers/Gd/Modifiers/FlipFlopModifierTest.php ================================================ readTestImage('tile.png'); $this->assertEquals('b4e000', $image->pickColor(0, 0)->toHex()); $image->modify(new FlipModifier()); $this->assertTransparency($image->pickColor(0, 0)); } public function testFlopImage(): void { $image = $this->readTestImage('tile.png'); $this->assertEquals('b4e000', $image->pickColor(0, 0)->toHex()); $image->modify(new FlopModifier()); $this->assertTransparency($image->pickColor(0, 0)); } } ================================================ FILE: tests/Unit/Drivers/Gd/Modifiers/GammaModifierTest.php ================================================ readTestImage('trim.png'); $this->assertEquals('00aef0', $image->pickColor(0, 0)->toHex()); $image->modify(new GammaModifier(2.1)); $this->assertEquals('00d5f8', $image->pickColor(0, 0)->toHex()); } } ================================================ FILE: tests/Unit/Drivers/Gd/Modifiers/GreyscaleModifierTest.php ================================================ readTestImage('trim.png'); $this->assertFalse($image->pickColor(0, 0)->isGreyscale()); $image->modify(new GreyscaleModifier()); $this->assertTrue($image->pickColor(0, 0)->isGreyscale()); } } ================================================ FILE: tests/Unit/Drivers/Gd/Modifiers/InvertModifierTest.php ================================================ readTestImage('trim.png'); $this->assertEquals('00aef0', $image->pickColor(0, 0)->toHex()); $this->assertEquals('ffa601', $image->pickColor(25, 25)->toHex()); $image->modify(new InvertModifier()); $this->assertEquals('ff510f', $image->pickColor(0, 0)->toHex()); $this->assertEquals('0059fe', $image->pickColor(25, 25)->toHex()); } } ================================================ FILE: tests/Unit/Drivers/Gd/Modifiers/PadModifierTest.php ================================================ readTestImage('blue.gif'); $this->assertEquals(16, $image->width()); $this->assertEquals(16, $image->height()); $image->modify(new PadModifier(30, 20, 'f00')); $this->assertEquals(30, $image->width()); $this->assertEquals(20, $image->height()); $this->assertColor(255, 0, 0, 255, $image->pickColor(0, 0)); $this->assertColor(255, 0, 0, 255, $image->pickColor(0, 19)); $this->assertColor(255, 0, 0, 255, $image->pickColor(29, 0)); $this->assertColor(255, 0, 0, 255, $image->pickColor(29, 19)); $this->assertColor(255, 0, 0, 255, $image->pickColor(6, 2)); $this->assertColor(255, 0, 0, 255, $image->pickColor(7, 1)); $this->assertColor(255, 0, 0, 255, $image->pickColor(6, 17)); $this->assertColor(255, 0, 0, 255, $image->pickColor(7, 18)); $this->assertColor(255, 0, 0, 255, $image->pickColor(23, 1)); $this->assertColor(255, 0, 0, 255, $image->pickColor(23, 2)); $this->assertColor(255, 0, 0, 255, $image->pickColor(23, 17)); $this->assertColor(255, 0, 0, 255, $image->pickColor(23, 18)); $this->assertColor(100, 100, 255, 255, $image->pickColor(7, 2)); $this->assertColor(100, 100, 255, 255, $image->pickColor(22, 2)); $this->assertColor(100, 100, 255, 255, $image->pickColor(7, 17)); $this->assertColor(100, 100, 255, 255, $image->pickColor(22, 17)); } } ================================================ FILE: tests/Unit/Drivers/Gd/Modifiers/PixelateModifierTest.php ================================================ readTestImage('trim.png'); $this->assertEquals('00aef0', $image->pickColor(0, 0)->toHex()); $this->assertEquals('00aef0', $image->pickColor(14, 14)->toHex()); $image->modify(new PixelateModifier(10)); $this->assertEquals('00aef0', $image->pickColor(0, 0)->toHex()); $this->assertEquals('6aaa8b', $image->pickColor(14, 14)->toHex()); } } ================================================ FILE: tests/Unit/Drivers/Gd/Modifiers/PlaceModifierTest.php ================================================ readTestImage('test.jpg'); $this->assertEquals('febc44', $image->pickColor(300, 25)->toHex()); $image->modify(new PlaceModifier($this->getTestResourcePath('circle.png'), 'top-right', 0, 0)); $this->assertEquals('32250d', $image->pickColor(300, 25)->toHex()); } public function testColorChangeOpacityPng(): void { $image = $this->readTestImage('test.jpg'); $this->assertEquals('febc44', $image->pickColor(300, 25)->toHex()); $image->modify(new PlaceModifier($this->getTestResourcePath('circle.png'), 'top-right', 0, 0, 50)); $this->assertColor(152, 112, 40, 255, $image->pickColor(300, 25), tolerance: 1); $this->assertColor(255, 202, 107, 255, $image->pickColor(274, 5), tolerance: 1); } public function testColorChangeOpacityJpeg(): void { $image = $this->createTestImage(16, 16)->fill('0000ff'); $this->assertEquals('0000ff', $image->pickColor(10, 10)->toHex()); $image->modify(new PlaceModifier($this->getTestResourcePath('exif.jpg'), opacity: 50)); $this->assertColor(127, 83, 127, 255, $image->pickColor(10, 10), tolerance: 1); } } ================================================ FILE: tests/Unit/Drivers/Gd/Modifiers/QuantizeColorsModifierTest.php ================================================ readTestImage('gradient.bmp'); $this->assertColorCount(15, $image); $image->modify(new QuantizeColorsModifier(4)); $this->assertColorCount(4, $image); } public function testNoColorReduction(): void { $image = $this->readTestImage('gradient.bmp'); $this->assertColorCount(15, $image); $image->modify(new QuantizeColorsModifier(150)); $this->assertColorCount(15, $image); } public function testInvalidColorInput(): void { $image = $this->readTestImage('gradient.bmp'); $this->expectException(InputException::class); $image->modify(new QuantizeColorsModifier(0)); } private function assertColorCount(int $count, ImageInterface $image): void { $colors = []; $width = $image->width(); $height = $image->height(); for ($x = 0; $x < $width; $x++) { for ($y = 0; $y < $height; $y++) { $rgb = imagecolorat($image->core()->native(), $x, $y); $color = imagecolorsforindex($image->core()->native(), $rgb); $color = implode('-', $color); $colors[$color] = $color; } } $this->assertEquals(count($colors), $count); } public function testVerifyColorValueAfterQuantization(): void { $image = $this->createTestImage(3, 2)->fill('f00'); $image->modify(new QuantizeColorsModifier(1)); $this->assertColor(255, 0, 0, 255, $image->pickColor(1, 1), 4); } } ================================================ FILE: tests/Unit/Drivers/Gd/Modifiers/RemoveAnimationModifierTest.php ================================================ readTestImage('animation.gif'); $this->assertEquals(8, count($image)); $result = $image->modify(new RemoveAnimationModifier(2)); $this->assertEquals(1, count($image)); $this->assertEquals(1, count($result)); } public function testApplyPercent(): void { $image = $this->readTestImage('animation.gif'); $this->assertEquals(8, count($image)); $result = $image->modify(new RemoveAnimationModifier('20%')); $this->assertEquals(1, count($image)); $this->assertEquals(1, count($result)); } public function testApplyNonAnimated(): void { $image = $this->readTestImage('test.jpg'); $this->assertEquals(1, count($image)); $result = $image->modify(new RemoveAnimationModifier()); $this->assertEquals(1, count($image)); $this->assertEquals(1, count($result)); } public function testApplyInvalid(): void { $image = $this->readTestImage('animation.gif'); $this->expectException(InputException::class); $image->modify(new RemoveAnimationModifier('test')); } } ================================================ FILE: tests/Unit/Drivers/Gd/Modifiers/ResizeCanvasModifierTest.php ================================================ createTestImage(1, 1); $this->assertEquals(1, $image->width()); $this->assertEquals(1, $image->height()); $image->modify(new ResizeCanvasModifier(3, 3, 'ff0', 'center')); $this->assertEquals(3, $image->width()); $this->assertEquals(3, $image->height()); $this->assertColor(255, 255, 0, 255, $image->pickColor(0, 0)); $this->assertColor(255, 0, 0, 255, $image->pickColor(1, 1)); $this->assertColor(255, 255, 0, 255, $image->pickColor(2, 2)); } public function testModifyWithTransparency(): void { $image = $this->readTestImage('tile.png'); $this->assertEquals(16, $image->width()); $this->assertEquals(16, $image->height()); $image->modify(new ResizeCanvasModifier(18, 18, 'ff0', 'center')); $this->assertEquals(18, $image->width()); $this->assertEquals(18, $image->height()); $this->assertColor(255, 255, 0, 255, $image->pickColor(0, 0)); $this->assertColor(180, 224, 0, 255, $image->pickColor(1, 1)); $this->assertColor(180, 224, 0, 255, $image->pickColor(2, 2)); $this->assertColor(255, 255, 0, 255, $image->pickColor(17, 17)); $this->assertTransparency($image->pickColor(12, 1)); $image = $this->createTestImage(16, 16); $image->modify(new ResizeCanvasModifier(32, 32, '00f5', 'center')); $this->assertEquals(32, $image->width()); $this->assertEquals(32, $image->height()); $this->assertColor(0, 0, 255, 85, $image->pickColor(5, 5)); $this->assertColor(0, 0, 255, 85, $image->pickColor(16, 5)); $this->assertColor(0, 0, 255, 85, $image->pickColor(30, 5)); $this->assertColor(0, 0, 255, 85, $image->pickColor(5, 16)); $this->assertColor(255, 0, 0, 255, $image->pickColor(16, 16)); $this->assertColor(0, 0, 255, 85, $image->pickColor(30, 16)); $this->assertColor(0, 0, 255, 85, $image->pickColor(5, 30)); $this->assertColor(0, 0, 255, 85, $image->pickColor(16, 30)); $this->assertColor(0, 0, 255, 85, $image->pickColor(30, 30)); } public function testModifyEdge(): void { $image = $this->createTestImage(1, 1); $this->assertColor(255, 0, 0, 255, $image->pickColor(0, 0)); $image->modify(new ResizeCanvasModifier(null, 2, 'ff0', 'bottom')); $this->assertEquals(1, $image->width()); $this->assertEquals(2, $image->height()); $this->assertColor(255, 255, 0, 255, $image->pickColor(0, 0)); $this->assertColor(255, 0, 0, 255, $image->pickColor(0, 1)); } } ================================================ FILE: tests/Unit/Drivers/Gd/Modifiers/ResizeCanvasRelativeModifierTest.php ================================================ createTestImage(1, 1); $this->assertEquals(1, $image->width()); $this->assertEquals(1, $image->height()); $image->modify(new ResizeCanvasRelativeModifier(2, 2, 'ff0', 'center')); $this->assertEquals(3, $image->width()); $this->assertEquals(3, $image->height()); $this->assertColor(255, 255, 0, 255, $image->pickColor(0, 0)); $this->assertColor(255, 0, 0, 255, $image->pickColor(1, 1)); $this->assertColor(255, 255, 0, 255, $image->pickColor(2, 2)); } public function testModifyWithTransparency(): void { $image = $this->readTestImage('tile.png'); $this->assertEquals(16, $image->width()); $this->assertEquals(16, $image->height()); $image->modify(new ResizeCanvasRelativeModifier(2, 2, 'ff0', 'center')); $this->assertEquals(18, $image->width()); $this->assertEquals(18, $image->height()); $this->assertColor(255, 255, 0, 255, $image->pickColor(0, 0)); $this->assertColor(180, 224, 0, 255, $image->pickColor(1, 1)); $this->assertColor(180, 224, 0, 255, $image->pickColor(2, 2)); $this->assertColor(255, 255, 0, 255, $image->pickColor(17, 17)); $this->assertTransparency($image->pickColor(12, 1)); } } ================================================ FILE: tests/Unit/Drivers/Gd/Modifiers/ResizeModifierTest.php ================================================ readTestImage('blocks.png'); $this->assertEquals(640, $image->width()); $this->assertEquals(480, $image->height()); $image->modify(new ResizeModifier(200, 100)); $this->assertEquals(200, $image->width()); $this->assertEquals(100, $image->height()); $this->assertColor(255, 0, 0, 255, $image->pickColor(150, 70)); $this->assertColor(0, 255, 0, 255, $image->pickColor(125, 70)); $this->assertColor(0, 0, 255, 255, $image->pickColor(130, 54)); $this->assertTransparency($image->pickColor(170, 30)); } } ================================================ FILE: tests/Unit/Drivers/Gd/Modifiers/ResolutionModifierTest.php ================================================ readTestImage('test.jpg'); $this->assertEquals(72.0, $image->resolution()->x()); $this->assertEquals(72.0, $image->resolution()->y()); $image->modify(new ResolutionModifier(1, 2)); $this->assertEquals(1.0, $image->resolution()->x()); $this->assertEquals(2.0, $image->resolution()->y()); } } ================================================ FILE: tests/Unit/Drivers/Gd/Modifiers/RotateModifierTest.php ================================================ readTestImage('test.jpg'); $this->assertEquals(320, $image->width()); $this->assertEquals(240, $image->height()); $image->modify(new RotateModifier(90, 'fff')); $this->assertEquals(240, $image->width()); $this->assertEquals(320, $image->height()); } } ================================================ FILE: tests/Unit/Drivers/Gd/Modifiers/SharpenModifierTest.php ================================================ readTestImage('trim.png'); $this->assertEquals('60ab96', $image->pickColor(15, 14)->toHex()); $image->modify(new SharpenModifier(10)); $this->assertEquals('4daba7', $image->pickColor(15, 14)->toHex()); } } ================================================ FILE: tests/Unit/Drivers/Gd/Modifiers/TextModifierTest.php ================================================ setColor('ff0055'); $modifier = new class ('test', new Point(), $font) extends TextModifier { public function test(): ColorInterface { return $this->textColor(); } }; $modifier->setDriver(new Driver()); $this->assertInstanceOf(ColorInterface::class, $modifier->test()); } } ================================================ FILE: tests/Unit/Drivers/Gd/Modifiers/TrimModifierTest.php ================================================ readTestImage('trim.png'); $this->assertEquals(50, $image->width()); $this->assertEquals(50, $image->height()); $image->modify(new TrimModifier()); $this->assertEquals(28, $image->width()); $this->assertEquals(28, $image->height()); } public function testTrimGradient(): void { $image = $this->readTestImage('radial.png'); $this->assertEquals(50, $image->width()); $this->assertEquals(50, $image->height()); $image->modify(new TrimModifier(50)); $this->assertEquals(35, $image->width()); $this->assertEquals(35, $image->height()); } public function testTrimHighTolerance(): void { $image = $this->readTestImage('trim.png'); $this->assertEquals(50, $image->width()); $this->assertEquals(50, $image->height()); $image->modify(new TrimModifier(1000000)); $this->assertEquals(1, $image->width()); $this->assertEquals(1, $image->height()); $this->assertColor(255, 255, 255, 0, $image->pickColor(0, 0)); } public function testTrimAnimated(): void { $image = $this->readTestImage('animation.gif'); $this->expectException(NotSupportedException::class); $image->modify(new TrimModifier()); } } ================================================ FILE: tests/Unit/Drivers/Imagick/Analyzers/ColorspaceAnalyzerTest.php ================================================ readTestImage('tile.png'); $analyzer = new ColorspaceAnalyzer(); $analyzer->setDriver(new Driver()); $result = $analyzer->analyze($image); $this->assertInstanceOf(ColorspaceInterface::class, $result); } } ================================================ FILE: tests/Unit/Drivers/Imagick/Analyzers/HeightAnalyzerTest.php ================================================ readTestImage('tile.png'); $analyzer = new HeightAnalyzer(); $analyzer->setDriver(new Driver()); $result = $analyzer->analyze($image); $this->assertEquals(16, $result); } } ================================================ FILE: tests/Unit/Drivers/Imagick/Analyzers/PixelColorAnalyzerTest.php ================================================ readTestImage('tile.png'); $analyzer = new PixelColorAnalyzer(0, 0); $analyzer->setDriver(new Driver()); $result = $analyzer->analyze($image); $this->assertInstanceOf(ColorInterface::class, $result); $this->assertEquals('b4e000', $result->toHex()); } } ================================================ FILE: tests/Unit/Drivers/Imagick/Analyzers/PixelColorsAnalyzerTest.php ================================================ readTestImage('animation.gif'); $analyzer = new PixelColorsAnalyzer(0, 0); $analyzer->setDriver(new Driver()); $result = $analyzer->analyze($image); $this->assertInstanceOf(Collection::class, $result); $colors = array_map(fn(ColorInterface $color) => $color->toHex(), $result->toArray()); $this->assertEquals($colors, ["394b63", "394b63", "394b63", "ffa601", "ffa601", "ffa601", "ffa601", "394b63"]); } public function testAnalyzeNonAnimated(): void { $image = $this->readTestImage('tile.png'); $analyzer = new PixelColorsAnalyzer(0, 0); $analyzer->setDriver(new Driver()); $result = $analyzer->analyze($image); $this->assertInstanceOf(Collection::class, $result); $this->assertInstanceOf(ColorInterface::class, $result->first()); $this->assertEquals('b4e000', $result->first()->toHex()); } } ================================================ FILE: tests/Unit/Drivers/Imagick/Analyzers/ProfileAnalyzerTest.php ================================================ readTestImage('tile.png'); $analyzer = new ProfileAnalyzer(); $analyzer->setDriver(new Driver()); $this->expectException(ColorException::class); $analyzer->analyze($image); } } ================================================ FILE: tests/Unit/Drivers/Imagick/Analyzers/ResolutionAnalyzerTest.php ================================================ readTestImage('300dpi.png'); $analyzer = new ResolutionAnalyzer(); $analyzer->setDriver(new Driver()); $result = $analyzer->analyze($image); $this->assertInstanceOf(Resolution::class, $result); $this->assertEquals(300, round($result->perInch()->x())); $this->assertEquals(300, round($result->perInch()->y())); } } ================================================ FILE: tests/Unit/Drivers/Imagick/Analyzers/WidthAnalyzerTest.php ================================================ readTestImage('tile.png'); $analyzer = new WidthAnalyzer(); $analyzer->setDriver(new Driver()); $result = $analyzer->analyze($image); $this->assertEquals(16, $result); } } ================================================ FILE: tests/Unit/Drivers/Imagick/ColorProcessorTest.php ================================================ colorToNative(new Color(255, 55, 0, 255)); $this->assertInstanceOf(ImagickPixel::class, $result); } public function testNativeToColor(): void { $processor = new ColorProcessor(new Colorspace()); $processor->nativeToColor(new ImagickPixel('rgb(255, 55, 0)')); } } ================================================ FILE: tests/Unit/Drivers/Imagick/CoreTest.php ================================================ newImage(10, 10, new ImagickPixel('red')); $imagick->addImage($im); $im = new Imagick(); $im->newImage(10, 10, new ImagickPixel('green')); $imagick->addImage($im); $im = new Imagick(); $im->newImage(10, 10, new ImagickPixel('blue')); $imagick->addImage($im); $this->core = new Core($imagick); } public function testAdd(): void { $imagick = new Imagick(); $imagick->newImage(100, 100, new ImagickPixel('red')); $this->assertEquals(3, $this->core->count()); $result = $this->core->add(new Frame($imagick)); $this->assertEquals(4, $this->core->count()); $this->assertInstanceOf(Core::class, $result); } public function testCount(): void { $this->assertEquals(3, $this->core->count()); } public function testIterator(): void { foreach ($this->core as $frame) { $this->assertInstanceOf(Frame::class, $frame); } } public function testNative(): void { $this->assertInstanceOf(Imagick::class, $this->core->native()); } public function testSetNative(): void { $imagick1 = new Imagick(); $imagick1->newImage(10, 10, new ImagickPixel('red')); $imagick2 = new Imagick(); $imagick2->newImage(10, 10, new ImagickPixel('red')); $core = new Core($imagick1); $this->assertEquals($imagick1, $core->native()); $core->setNative($imagick2); $this->assertEquals($imagick2, $core->native()); } public function testFrame(): void { $this->assertInstanceOf(Frame::class, $this->core->frame(0)); $this->assertInstanceOf(Frame::class, $this->core->frame(1)); $this->assertInstanceOf(Frame::class, $this->core->frame(2)); $this->expectException(AnimationException::class); $this->core->frame(3); } public function testSetGetLoops(): void { $this->assertEquals(0, $this->core->loops()); $result = $this->core->setLoops(12); $this->assertEquals(12, $this->core->loops()); $this->assertInstanceOf(Core::class, $result); } public function testHas(): void { $this->assertTrue($this->core->has(0)); $this->assertTrue($this->core->has(1)); $this->assertTrue($this->core->has(2)); $this->assertFalse($this->core->has(3)); } public function testPush(): void { $im = new Imagick(); $im->newImage(100, 100, new ImagickPixel('green')); $this->assertEquals(3, $this->core->count()); $result = $this->core->push(new Frame($im)); $this->assertEquals(4, $this->core->count()); $this->assertEquals(4, $result->count()); } public function testGet(): void { $this->assertInstanceOf(Frame::class, $this->core->get(0)); $this->assertInstanceOf(Frame::class, $this->core->get(1)); $this->assertInstanceOf(Frame::class, $this->core->get(2)); $this->assertNull($this->core->get(3)); $this->assertEquals('foo', $this->core->get(3, 'foo')); } public function testEmpty(): void { $result = $this->core->empty(); $this->assertEquals(0, $this->core->count()); $this->assertEquals(0, $result->count()); } public function testSlice(): void { $this->assertEquals(3, $this->core->count()); $result = $this->core->slice(1, 2); $this->assertEquals(2, $this->core->count()); $this->assertEquals(2, $result->count()); } public function testFirst(): void { $this->assertInstanceOf(Frame::class, $this->core->first()); } public function testLast(): void { $this->assertInstanceOf(Frame::class, $this->core->last()); } } ================================================ FILE: tests/Unit/Drivers/Imagick/Decoders/Base64ImageDecoderTest.php ================================================ decoder = new Base64ImageDecoder(); $this->decoder->setDriver(new Driver()); } public function testDecode(): void { $result = $this->decoder->decode( base64_encode($this->getTestResourceData('blue.gif')) ); $this->assertInstanceOf(Image::class, $result); } public function testDecoderInvalid(): void { $this->expectException(DecoderException::class); $this->decoder->decode('test'); } } ================================================ FILE: tests/Unit/Drivers/Imagick/Decoders/BinaryImageDecoderTest.php ================================================ decoder = new BinaryImageDecoder(); $this->decoder->setDriver(new Driver()); } public function testDecodePng(): void { $image = $this->decoder->decode(file_get_contents($this->getTestResourcePath('tile.png'))); $this->assertInstanceOf(Image::class, $image); $this->assertInstanceOf(RgbColorspace::class, $image->colorspace()); $this->assertEquals(16, $image->width()); $this->assertEquals(16, $image->height()); $this->assertCount(1, $image); } public function testDecodeGif(): void { $image = $this->decoder->decode(file_get_contents($this->getTestResourcePath('red.gif'))); $this->assertInstanceOf(Image::class, $image); $this->assertEquals(16, $image->width()); $this->assertEquals(16, $image->height()); $this->assertCount(1, $image); } public function testDecodeAnimatedGif(): void { $image = $this->decoder->decode(file_get_contents($this->getTestResourcePath('cats.gif'))); $this->assertInstanceOf(Image::class, $image); $this->assertEquals(75, $image->width()); $this->assertEquals(50, $image->height()); $this->assertCount(4, $image); } public function testDecodeJpegWithExif(): void { $image = $this->decoder->decode(file_get_contents($this->getTestResourcePath('exif.jpg'))); $this->assertInstanceOf(Image::class, $image); $this->assertEquals(16, $image->width()); $this->assertEquals(16, $image->height()); $this->assertCount(1, $image); $this->assertEquals('Oliver Vogel', $image->exif('IFD0.Artist')); } public function testDecodeCmykImage(): void { $image = $this->decoder->decode(file_get_contents($this->getTestResourcePath('cmyk.jpg'))); $this->assertInstanceOf(Image::class, $image); $this->assertInstanceOf(CmykColorspace::class, $image->colorspace()); } public function testDecodeNonString(): void { $this->expectException(DecoderException::class); $this->decoder->decode(new stdClass()); } } ================================================ FILE: tests/Unit/Drivers/Imagick/Decoders/DataUriImageDecoderTest.php ================================================ decoder = new DataUriImageDecoder(); $this->decoder->setDriver(new Driver()); } public function testDecode(): void { $result = $this->decoder->decode( sprintf('data:image/jpeg;base64,%s', base64_encode($this->getTestResourceData('blue.gif'))) ); $this->assertInstanceOf(Image::class, $result); } public function testDecoderNonString(): void { $this->expectException(DecoderException::class); $this->decoder->decode(new stdClass()); } public function testDecoderInvalid(): void { $this->expectException(DecoderException::class); $this->decoder->decode('invalid'); } public function testDecoderNonImage(): void { $this->expectException(DecoderException::class); $this->decoder->decode('data:text/plain;charset=utf-8,test'); } } ================================================ FILE: tests/Unit/Drivers/Imagick/Decoders/EncodedImageObjectDecoderTest.php ================================================ decoder = new EncodedImageObjectDecoder(); $this->decoder->setDriver(new Driver()); } public function testDecode(): void { $result = $this->decoder->decode(new EncodedImage($this->getTestResourceData())); $this->assertInstanceOf(Image::class, $result); } } ================================================ FILE: tests/Unit/Drivers/Imagick/Decoders/FilePathImageDecoderTest.php ================================================ decoder = new FilePathImageDecoder(); $this->decoder->setDriver(new Driver()); } #[DataProvider('validFormatPathsProvider')] public function testDecode(string $path, bool $result): void { if ($result === false) { $this->expectException(DecoderException::class); } $result = $this->decoder->decode($path); if ($result === true) { $this->assertInstanceOf(Image::class, $result); } } public static function validFormatPathsProvider(): Generator { yield [self::getTestResourcePath('cats.gif'), true]; yield [self::getTestResourcePath('animation.gif'), true]; yield [self::getTestResourcePath('red.gif'), true]; yield [self::getTestResourcePath('green.gif'), true]; yield [self::getTestResourcePath('blue.gif'), true]; yield [self::getTestResourcePath('gradient.bmp'), true]; yield [self::getTestResourcePath('circle.png'), true]; yield ['no-path', false]; yield [str_repeat('x', PHP_MAXPATHLEN + 1), false]; } } ================================================ FILE: tests/Unit/Drivers/Imagick/Decoders/FilePointerImageDecoderTest.php ================================================ setDriver(new Driver()); $fp = fopen($this->getTestResourcePath('test.jpg'), 'r'); $result = $decoder->decode($fp); $this->assertInstanceOf(Image::class, $result); } } ================================================ FILE: tests/Unit/Drivers/Imagick/Decoders/ImageObjectDecoderTest.php ================================================ decode($this->readTestImage('blue.gif')); $this->assertInstanceOf(Image::class, $result); } } ================================================ FILE: tests/Unit/Drivers/Imagick/Decoders/NativeObjectDecoderTest.php ================================================ decoder = new NativeObjectDecoder(); $this->decoder->setDriver(new Driver()); } public function testDecode(): void { $native = new Imagick(); $native->newImage(3, 2, new ImagickPixel('red'), 'png'); $result = $this->decoder->decode($native); $this->assertInstanceOf(Image::class, $result); } } ================================================ FILE: tests/Unit/Drivers/Imagick/Decoders/SplFileInfoImageDecoderTest.php ================================================ setDriver(new Driver()); $result = $decoder->decode( new SplFileInfo($this->getTestResourcePath('blue.gif')) ); $this->assertInstanceOf(Image::class, $result); } } ================================================ FILE: tests/Unit/Drivers/Imagick/DriverTest.php ================================================ driver = new Driver(); } public function testId(): void { $this->assertEquals('Imagick', $this->driver->id()); } public function testCreateImage(): void { $image = $this->driver->createImage(3, 2); $this->assertInstanceOf(ImageInterface::class, $image); $this->assertEquals(3, $image->width()); $this->assertEquals(2, $image->height()); } public function testCreateAnimation(): void { $image = $this->driver->createAnimation(function ($animation): void { $animation->add($this->getTestResourcePath('red.gif'), .25); $animation->add($this->getTestResourcePath('green.gif'), .25); })->setLoops(5); $this->assertInstanceOf(ImageInterface::class, $image); $this->assertEquals(16, $image->width()); $this->assertEquals(16, $image->height()); $this->assertEquals(5, $image->loops()); $this->assertEquals(2, $image->count()); } public function testHandleInputImage(): void { $result = $this->driver->handleInput($this->getTestResourcePath('test.jpg')); $this->assertInstanceOf(ImageInterface::class, $result); } public function testHandleInputColor(): void { $result = $this->driver->handleInput('ffffff'); $this->assertInstanceOf(ColorInterface::class, $result); } public function testHandleInputObjects(): void { $result = $this->driver->handleInput('ffffff', [ new HexColorDecoder() ]); $this->assertInstanceOf(ColorInterface::class, $result); } public function testHandleInputClassnames(): void { $result = $this->driver->handleInput('ffffff', [ HexColorDecoder::class ]); $this->assertInstanceOf(ColorInterface::class, $result); } public function testColorProcessor(): void { $result = $this->driver->colorProcessor(new Colorspace()); $this->assertInstanceOf(ColorProcessorInterface::class, $result); } #[DataProvider('supportsDataProvider')] public function testSupports(bool $result, mixed $identifier): void { $this->assertEquals($result, $this->driver->supports($identifier)); } public static function supportsDataProvider(): Generator { yield [true, Format::JPEG]; yield [true, MediaType::IMAGE_JPEG]; yield [true, MediaType::IMAGE_JPG]; yield [true, FileExtension::JPG]; yield [true, FileExtension::JPEG]; yield [true, 'jpg']; yield [true, 'jpeg']; yield [true, 'image/jpg']; yield [true, 'image/jpeg']; yield [true, Format::WEBP]; yield [true, MediaType::IMAGE_WEBP]; yield [true, MediaType::IMAGE_X_WEBP]; yield [true, FileExtension::WEBP]; yield [true, 'webp']; yield [true, 'image/webp']; yield [true, 'image/x-webp']; yield [true, Format::GIF]; yield [true, MediaType::IMAGE_GIF]; yield [true, FileExtension::GIF]; yield [true, 'gif']; yield [true, 'image/gif']; yield [true, Format::PNG]; yield [true, MediaType::IMAGE_PNG]; yield [true, MediaType::IMAGE_X_PNG]; yield [true, FileExtension::PNG]; yield [true, 'png']; yield [true, 'image/png']; yield [true, 'image/x-png']; yield [true, Format::AVIF]; yield [true, MediaType::IMAGE_AVIF]; yield [true, MediaType::IMAGE_X_AVIF]; yield [true, FileExtension::AVIF]; yield [true, 'avif']; yield [true, 'image/avif']; yield [true, 'image/x-avif']; yield [true, Format::BMP]; yield [true, FileExtension::BMP]; yield [true, MediaType::IMAGE_BMP]; yield [true, MediaType::IMAGE_MS_BMP]; yield [true, MediaType::IMAGE_X_BITMAP]; yield [true, MediaType::IMAGE_X_BMP]; yield [true, MediaType::IMAGE_X_MS_BMP]; yield [true, MediaType::IMAGE_X_WINDOWS_BMP]; yield [true, MediaType::IMAGE_X_WIN_BITMAP]; yield [true, MediaType::IMAGE_X_XBITMAP]; yield [true, 'bmp']; yield [true, 'image/bmp']; yield [true, 'image/ms-bmp']; yield [true, 'image/x-bitmap']; yield [true, 'image/x-bmp']; yield [true, 'image/x-ms-bmp']; yield [true, 'image/x-windows-bmp']; yield [true, 'image/x-win-bitmap']; yield [true, 'image/x-xbitmap']; yield [true, Format::TIFF]; yield [true, MediaType::IMAGE_TIFF]; yield [true, FileExtension::TIFF]; yield [true, FileExtension::TIF]; yield [true, 'tif']; yield [true, 'tiff']; yield [true, 'image/tiff']; yield [true, Format::JP2]; yield [true, MediaType::IMAGE_JP2]; yield [true, MediaType::IMAGE_JPX]; yield [true, MediaType::IMAGE_JPM]; yield [true, FileExtension::TIFF]; yield [true, FileExtension::TIF]; yield [true, FileExtension::JP2]; yield [true, FileExtension::J2K]; yield [true, FileExtension::JPF]; yield [true, FileExtension::JPM]; yield [true, FileExtension::JPG2]; yield [true, FileExtension::J2C]; yield [true, FileExtension::JPC]; yield [true, FileExtension::JPX]; yield [true, 'jp2']; yield [true, 'j2k']; yield [true, 'jpf']; yield [true, 'jpm']; yield [true, 'jpg2']; yield [true, 'j2c']; yield [true, 'jpc']; yield [true, 'jpx']; yield [true, Format::HEIC]; yield [true, MediaType::IMAGE_HEIC]; yield [true, MediaType::IMAGE_HEIF]; yield [true, FileExtension::HEIC]; yield [true, FileExtension::HEIF]; yield [true, 'heic']; yield [true, 'heif']; yield [true, 'image/heic']; yield [true, 'image/heif']; yield [false, 'tga']; yield [false, 'image/tga']; yield [false, 'image/x-targa']; yield [false, 'foo']; yield [false, '']; } public function testVersion(): void { $this->assertTrue(is_string($this->driver->version())); } #[DataProvider('spezializeDataProvider')] public function testSpecialize(string $inputClassname, string $outputClassname): void { $this->assertInstanceOf($outputClassname, $this->driver->specialize(new $inputClassname())); } public static function spezializeDataProvider(): Generator { // specializing possible yield [GenericResizeModifier::class, ResizeModifier::class]; yield [GenericWidthAnalyzer::class, WidthAnalyzer::class]; yield [GenericPngEncoder::class, PngEncoder::class]; yield [GenericFilePathImageDecoder::class, FilePathImageDecoder::class]; // already specialized yield [ResizeModifier::class, ResizeModifier::class]; yield [WidthAnalyzer::class, WidthAnalyzer::class]; yield [PngEncoder::class, PngEncoder::class]; yield [FilePathImageDecoder::class, FilePathImageDecoder::class]; } public function testSpecializeFailure(): void { $this->expectException(NotSupportedException::class); $this->driver->specialize(new class () implements AnalyzerInterface, SpecializableInterface { protected DriverInterface $driver; public function analyze(ImageInterface $image): mixed { return true; } /** @return array **/ public function specializable(): array { return []; } public function setDriver(DriverInterface $driver): SpecializableInterface { return $this; } public function driver(): DriverInterface { return $this->driver; } }); } } ================================================ FILE: tests/Unit/Drivers/Imagick/Encoders/AvifEncoderTest.php ================================================ createTestImage(3, 2); $encoder = new AvifEncoder(10); $encoder->setDriver(new Driver()); $result = $encoder->encode($image); $this->assertMediaType('image/avif', $result); $this->assertEquals('image/avif', $result->mimetype()); } } ================================================ FILE: tests/Unit/Drivers/Imagick/Encoders/BmpEncoderTest.php ================================================ createTestImage(3, 2); $encoder = new BmpEncoder(); $result = $encoder->encode($image); $this->assertMediaType(['image/bmp', 'image/x-ms-bmp'], $result); $this->assertEquals('image/bmp', $result->mimetype()); } } ================================================ FILE: tests/Unit/Drivers/Imagick/Encoders/GifEncoderTest.php ================================================ createTestImage(3, 2); $encoder = new GifEncoder(); $result = $encoder->encode($image); $this->assertMediaType('image/gif', $result); $this->assertEquals('image/gif', $result->mimetype()); $this->assertFalse( Decoder::decode((string) $result)->getFirstFrame()->getImageDescriptor()->isInterlaced() ); } public function testEncodeInterlaced(): void { $image = $this->createTestImage(3, 2); $encoder = new GifEncoder(interlaced: true); $result = $encoder->encode($image); $this->assertMediaType('image/gif', $result); $this->assertEquals('image/gif', $result->mimetype()); $this->assertTrue( Decoder::decode((string) $result)->getFirstFrame()->getImageDescriptor()->isInterlaced() ); } public function testEncodeInterlacedAnimation(): void { $image = $this->createTestAnimation(); $encoder = new GifEncoder(interlaced: true); $result = $encoder->encode($image); $this->assertMediaType('image/gif', $result); $this->assertEquals('image/gif', $result->mimetype()); $this->assertTrue( Decoder::decode((string) $result)->getFirstFrame()->getImageDescriptor()->isInterlaced() ); } } ================================================ FILE: tests/Unit/Drivers/Imagick/Encoders/HeicEncoderTest.php ================================================ createTestImage(3, 2); $encoder = new HeicEncoder(75); $encoder->setDriver(new Driver()); $result = $encoder->encode($image); $this->assertMediaType('image/heic', $result); $this->assertEquals('image/heic', $result->mimetype()); } } ================================================ FILE: tests/Unit/Drivers/Imagick/Encoders/Jpeg2000EncoderTest.php ================================================ createTestImage(3, 2); $encoder = new Jpeg2000Encoder(75); $encoder->setDriver(new Driver()); $result = $encoder->encode($image); $this->assertMediaType('image/jp2', $result); $this->assertEquals('image/jp2', $result->mimetype()); } } ================================================ FILE: tests/Unit/Drivers/Imagick/Encoders/JpegEncoderTest.php ================================================ createTestImage(3, 2); $encoder = new JpegEncoder(75); $encoder->setDriver(new Driver()); $result = $encoder->encode($image); $this->assertMediaType('image/jpeg', $result); $this->assertEquals('image/jpeg', $result->mimetype()); } public function testEncodeProgressive(): void { $image = $this->createTestImage(3, 2); $encoder = new JpegEncoder(progressive: true); $encoder->setDriver(new Driver()); $result = $encoder->encode($image); $this->assertMediaType('image/jpeg', $result); $this->assertEquals('image/jpeg', $result->mimetype()); $this->assertTrue($this->isProgressiveJpeg($result)); } public function testEncodeStripExif(): void { $image = $this->readTestImage('exif.jpg'); $this->assertEquals('Oliver Vogel', $image->exif('IFD0.Artist')); $encoder = new JpegEncoder(strip: true); $encoder->setDriver(new Driver()); $result = $encoder->encode($image); $this->assertMediaType('image/jpeg', $result); $this->assertEquals('image/jpeg', $result->mimetype()); $this->assertEmpty(exif_read_data($result->toFilePointer())['IFD0.Artist'] ?? null); } public function testEncodeStripExifKeepICCProfiles(): void { $image = $this->readTestImage('cmyk.jpg'); $this->assertNotEmpty($image->core()->native()->getImageProfiles('icc')); $encoder = new JpegEncoder(strip: true); $encoder->setDriver(new Driver()); $result = $encoder->encode($image); $decoder = new FilePointerImageDecoder(); $decoder->setDriver(new Driver()); $image = $decoder->decode($result->toFilePointer()); $this->assertNotEmpty($image->core()->native()->getImageProfiles('icc')); } } ================================================ FILE: tests/Unit/Drivers/Imagick/Encoders/PngEncoderTest.php ================================================ createTestImage(3, 2); $encoder = new PngEncoder(); $result = $encoder->encode($image); $this->assertMediaType('image/png', $result); $this->assertEquals('image/png', $result->mimetype()); $this->assertFalse($this->isInterlacedPng($result)); } public function testEncodeInterlaced(): void { $image = $this->createTestImage(3, 2); $encoder = new PngEncoder(interlaced: true); $result = $encoder->encode($image); $this->assertMediaType('image/png', $result); $this->assertEquals('image/png', $result->mimetype()); $this->assertTrue($this->isInterlacedPng($result)); } #[DataProvider('indexedDataProvider')] public function testEncoderIndexed(ImageInterface $image, PngEncoder $encoder, string $result): void { $this->assertEquals( $result, $this->pngColorType($encoder->encode($image)), ); } public static function indexedDataProvider(): Generator { yield [ static::createTestImage(3, 2), // new new PngEncoder(indexed: false), 'truecolor-alpha', ]; yield [ static::createTestImage(3, 2), // new new PngEncoder(indexed: true), 'indexed', ]; yield [ static::createTestImage(3, 2)->fill('ccc'), // new grayscale new PngEncoder(indexed: true), 'indexed', ]; yield [ static::readTestImage('circle.png'), // truecolor-alpha new PngEncoder(indexed: false), 'truecolor-alpha', ]; yield [ static::readTestImage('circle.png'), // indexedcolor-alpha new PngEncoder(indexed: true), 'grayscale-alpha', // result should be 'indexed' but there seems to be no way to force this with imagick ]; yield [ static::readTestImage('tile.png'), // indexed new PngEncoder(indexed: false), 'truecolor-alpha', ]; yield [ static::readTestImage('tile.png'), // indexed new PngEncoder(indexed: true), 'indexed', ]; yield [ static::readTestImage('test.jpg'), // jpeg new PngEncoder(indexed: false), 'truecolor-alpha', ]; yield [ static::readTestImage('test.jpg'), // jpeg new PngEncoder(indexed: true), 'indexed', ]; } } ================================================ FILE: tests/Unit/Drivers/Imagick/Encoders/TiffEncoderTest.php ================================================ createTestImage(3, 2); $encoder = new TiffEncoder(); $encoder->setDriver(new Driver()); $result = $encoder->encode($image); $this->assertMediaType('image/tiff', $result); $this->assertEquals('image/tiff', $result->mimetype()); } } ================================================ FILE: tests/Unit/Drivers/Imagick/Encoders/WebpEncoderTest.php ================================================ createTestImage(3, 2); $encoder = new WebpEncoder(75); $encoder->setDriver(new Driver()); $result = $encoder->encode($image); $this->assertMediaType('image/webp', $result); $this->assertEquals('image/webp', $result->mimetype()); } } ================================================ FILE: tests/Unit/Drivers/Imagick/FontProcessorTest.php ================================================ boxSize( 'ABC', $this->testFont()->setSize(120), ); $this->assertInstanceOf(SizeInterface::class, $size); $this->assertEquals(163, $size->width()); $this->assertEquals(72, $size->height()); } public function testNativeFontSize(): void { $processor = new FontProcessor(); $font = new Font(); $font->setSize(14.2); $size = $processor->nativeFontSize($font); $this->assertEquals(14.2, $size); } public function testTextBlock(): void { $processor = new FontProcessor(); $result = $processor->textBlock( 'test', $this->testFont(), new Point(0, 0), ); $this->assertInstanceOf(TextBlock::class, $result); } public function testTypographicalSize(): void { $processor = new FontProcessor(); $result = $processor->typographicalSize($this->testFont()); $this->assertEquals(7, $result); } public function testCapHeight(): void { $processor = new FontProcessor(); $result = $processor->capHeight($this->testFont()); $this->assertEquals(7, $result); } public function testLeading(): void { $processor = new FontProcessor(); $result = $processor->leading($this->testFont()); $this->assertEquals(9, $result); } private function testFont(): Font { return new Font($this->getTestResourcePath('test.ttf')); } } ================================================ FILE: tests/Unit/Drivers/Imagick/FrameTest.php ================================================ newImage(3, 2, new ImagickPixel('red'), 'png'); $imagick->setImageDelay(125); // 1.25 seconds $imagick->setImageDispose(0); $imagick->setImagePage(3, 2, 8, 9); return new Frame($imagick); } public function testConstructor(): void { $frame = $this->getTestFrame(); $this->assertInstanceOf(Frame::class, $frame); } public function testGetSize(): void { $frame = $this->getTestFrame(); $this->assertInstanceOf(Rectangle::class, $frame->size()); } public function testSetGetDelay(): void { $frame = $this->getTestFrame(); $this->assertEquals(1.25, $frame->delay()); $result = $frame->setDelay(2.5); $this->assertInstanceOf(Frame::class, $result); $this->assertEquals(2.5, $frame->delay()); $this->assertEquals(250, $frame->native()->getImageDelay()); } public function testSetGetDispose(): void { $frame = $this->getTestFrame(); $this->assertEquals(0, $frame->dispose()); $result = $frame->setDispose(3); $this->assertInstanceOf(Frame::class, $result); $this->assertEquals(3, $frame->dispose()); } public function testSetGetOffsetLeft(): void { $frame = $this->getTestFrame(); $this->assertEquals(8, $frame->offsetLeft()); $result = $frame->setOffsetLeft(100); $this->assertInstanceOf(Frame::class, $result); $this->assertEquals(100, $frame->offsetLeft()); } public function testSetGetOffsetTop(): void { $frame = $this->getTestFrame(); $this->assertEquals(9, $frame->offsetTop()); $result = $frame->setOffsetTop(100); $this->assertInstanceOf(Frame::class, $result); $this->assertEquals(100, $frame->offsetTop()); } public function testSetGetOffset(): void { $frame = $this->getTestFrame(); $this->assertEquals(8, $frame->offsetLeft()); $this->assertEquals(9, $frame->offsetTop()); $result = $frame->setOffset(100, 200); $this->assertInstanceOf(Frame::class, $result); $this->assertEquals(100, $frame->offsetLeft()); $this->assertEquals(200, $frame->offsetTop()); } public function testToImage(): void { $frame = $this->getTestFrame(); $this->assertInstanceOf(Image::class, $frame->toImage(new Driver())); } } ================================================ FILE: tests/Unit/Drivers/Imagick/ImageTest.php ================================================ readImage($this->getTestResourcePath('animation.gif')); $this->image = new Image( new Driver(), new Core($imagick), new Collection([ 'test' => 'foo' ]), ); } public function testClone(): void { $image = $this->readTestImage('gradient.gif'); $clone = clone $image; $this->assertEquals(16, $image->width()); $this->assertEquals(16, $clone->width()); $result = $clone->crop(4, 4); $this->assertEquals(16, $image->width()); $this->assertEquals(4, $clone->width()); $this->assertEquals(4, $result->width()); $this->assertEquals('ff0000', $image->pickColor(0, 0)->toHex()); $this->assertTransparency($image->pickColor(1, 0)); $this->assertEquals('ff0000', $clone->pickColor(0, 0)->toHex()); $this->assertTransparency($clone->pickColor(1, 0)); } public function testDriver(): void { $this->assertInstanceOf(Driver::class, $this->image->driver()); } public function testCore(): void { $this->assertInstanceOf(Core::class, $this->image->core()); } public function testCount(): void { $this->assertEquals(8, $this->image->count()); } public function testIteration(): void { foreach ($this->image as $frame) { $this->assertInstanceOf(Frame::class, $frame); } } public function testIsAnimated(): void { $this->assertTrue($this->image->isAnimated()); } public function testSetGetLoops(): void { $this->assertEquals(3, $this->image->loops()); $result = $this->image->setLoops(10); $this->assertInstanceOf(ImageInterface::class, $result); $this->assertEquals(10, $this->image->loops()); } public function testSetGetOrigin(): void { $origin = $this->image->origin(); $this->assertInstanceOf(Origin::class, $origin); $this->image->setOrigin(new Origin('test1', 'test2')); $this->assertInstanceOf(Origin::class, $this->image->origin()); $this->assertEquals('test1', $this->image->origin()->mimetype()); $this->assertEquals('test2', $this->image->origin()->filePath()); } public function testRemoveAnimation(): void { $this->assertTrue($this->image->isAnimated()); $result = $this->image->removeAnimation(); $this->assertInstanceOf(ImageInterface::class, $result); $this->assertFalse($this->image->isAnimated()); } public function testSliceAnimation(): void { $this->assertEquals(8, $this->image->count()); $result = $this->image->sliceAnimation(0, 2); $this->assertInstanceOf(ImageInterface::class, $result); $this->assertEquals(2, $this->image->count()); } public function testExif(): void { $this->assertInstanceOf(Collection::class, $this->image->exif()); $this->assertEquals('foo', $this->image->exif('test')); } public function testModify(): void { $result = $this->image->modify(new GreyscaleModifier()); $this->assertInstanceOf(Image::class, $result); } public function testAnalyze(): void { $result = $this->image->analyze(new WidthAnalyzer()); $this->assertEquals(20, $result); } public function testEncode(): void { $result = $this->image->encode(new PngEncoder()); $this->assertInstanceOf(EncodedImage::class, $result); } public function testAutoEncode(): void { $result = $this->readTestImage('blue.gif')->encode(); $this->assertInstanceOf(EncodedImage::class, $result); $this->assertMediaType('image/gif', $result); } public function testEncodeByMediaType(): void { $result = $this->readTestImage('blue.gif')->encodeByMediaType(); $this->assertInstanceOf(EncodedImage::class, $result); $this->assertMediaType('image/gif', $result); $result = $this->readTestImage('blue.gif')->encodeByMediaType('image/png'); $this->assertInstanceOf(EncodedImage::class, $result); $this->assertMediaType('image/png', $result); } public function testEncodeByExtension(): void { $result = $this->readTestImage('blue.gif')->encodeByExtension(); $this->assertInstanceOf(EncodedImage::class, $result); $this->assertMediaType('image/gif', $result); $result = $this->readTestImage('blue.gif')->encodeByExtension('png'); $this->assertInstanceOf(EncodedImage::class, $result); $this->assertMediaType('image/png', $result); } public function testEncodeByPath(): void { $result = $this->readTestImage('blue.gif')->encodeByPath(); $this->assertInstanceOf(EncodedImage::class, $result); $this->assertMediaType('image/gif', $result); $result = $this->readTestImage('blue.gif')->encodeByPath('foo/bar.png'); $this->assertInstanceOf(EncodedImage::class, $result); $this->assertMediaType('image/png', $result); } public function testSaveAsFormat(): void { $path = __DIR__ . '/tmp.png'; $result = $this->readTestImage('blue.gif')->save($path); $this->assertInstanceOf(Image::class, $result); $this->assertFileExists($path); $this->assertMediaType('image/png', file_get_contents($path)); unlink($path); } public function testSaveFallback(): void { $path = __DIR__ . '/tmp.unknown'; $result = $this->readTestImage('blue.gif')->save($path); $this->assertInstanceOf(Image::class, $result); $this->assertFileExists($path); $this->assertMediaType('image/gif', file_get_contents($path)); unlink($path); } public function testSaveUndeterminedPath(): void { $this->expectException(EncoderException::class); $this->createTestImage(2, 3)->save(); } public function testWidthHeightSize(): void { $this->assertEquals(20, $this->image->width()); $this->assertEquals(15, $this->image->height()); $this->assertInstanceOf(SizeInterface::class, $this->image->size()); } public function testSetGetColorspace(): void { $this->assertInstanceOf(ColorspaceInterface::class, $this->image->colorspace()); $this->assertInstanceOf(RgbColorspace::class, $this->image->colorspace()); $result = $this->image->setColorspace(CmykColorspace::class); $this->assertInstanceOf(ImageInterface::class, $result); $this->assertInstanceOf(CmykColorspace::class, $this->image->colorspace()); } public function testSetGetResolution(): void { $resolution = $this->image->resolution(); $this->assertInstanceOf(ResolutionInterface::class, $resolution); $this->assertEquals(0, $resolution->x()); $this->assertEquals(0, $resolution->y()); $result = $this->image->setResolution(300, 300); $resolution = $this->image->resolution(); $this->assertInstanceOf(ImageInterface::class, $result); $this->assertEquals(300, $resolution->x()); $this->assertEquals(300, $resolution->y()); } public function testPickColor(): void { $this->assertInstanceOf(ColorInterface::class, $this->image->pickColor(0, 0)); $this->assertInstanceOf(ColorInterface::class, $this->image->pickColor(0, 0, 1)); } public function testPickColors(): void { $result = $this->image->pickColors(0, 0); $this->assertInstanceOf(Collection::class, $result); $this->assertEquals(8, $result->count()); } public function testProfile(): void { $this->expectException(ColorException::class); $this->image->profile(); } public function testReduceColors(): void { $image = $this->readTestImage(); $result = $image->reduceColors(8); $this->assertInstanceOf(ImageInterface::class, $result); } public function testSharpen(): void { $this->assertInstanceOf(Image::class, $this->image->sharpen(12)); } public function testBlendTransparencyDefault(): void { $image = $this->readTestImage('gradient.gif'); $this->assertColor(0, 0, 0, 0, $image->pickColor(1, 0)); $result = $image->blendTransparency(); $this->assertColor(255, 255, 255, 255, $image->pickColor(1, 0)); $this->assertColor(255, 255, 255, 255, $result->pickColor(1, 0)); } public function testBlendTransparencyArgument(): void { $image = $this->readTestImage('gradient.gif'); $this->assertColor(0, 0, 0, 0, $image->pickColor(1, 0)); $result = $image->blendTransparency('ff5500'); $this->assertColor(255, 85, 0, 255, $image->pickColor(1, 0)); $this->assertColor(255, 85, 0, 255, $result->pickColor(1, 0)); } public function testBlendTransparencyIgnoreTransparencyInBlendingColor(): void { $image = $this->readTestImage('gradient.gif'); $this->assertColor(0, 0, 0, 0, $image->pickColor(1, 0)); $result = $image->blendTransparency('ff550055'); $this->assertColor(255, 85, 0, 255, $image->pickColor(1, 0)); $this->assertColor(255, 85, 0, 255, $result->pickColor(1, 0)); } public function testToJpeg(): void { $this->assertMediaType('image/jpeg', $this->image->toJpeg()); $this->assertMediaType('image/jpeg', $this->image->toJpg()); } public function testToJpeg2000(): void { $this->assertMediaType('image/jp2', $this->image->toJpeg2000()); $this->assertMediaType('image/jp2', $this->image->toJp2()); } public function testToPng(): void { $this->assertMediaType('image/png', $this->image->toPng()); } public function testToGif(): void { $this->assertMediaType('image/gif', $this->image->toGif()); } public function testToWebp(): void { $this->assertMediaType('image/webp', $this->image->toWebp()); } public function testToBitmap(): void { $this->assertMediaTypeBitmap($this->image->toBitmap()); $this->assertMediaTypeBitmap($this->image->toBmp()); } public function testToAvif(): void { $this->assertMediaType('image/avif', $this->image->toAvif()); } public function testToTiff(): void { $this->assertMediaType('image/tiff', $this->image->toTiff()); $this->assertMediaType('image/tiff', $this->image->toTif()); } public function testToHeic(): void { $this->assertMediaType('image/heic', $this->image->toHeic()); } public function testInvert(): void { $image = $this->readTestImage('trim.png'); $this->assertEquals('00aef0', $image->pickColor(0, 0)->toHex()); $this->assertEquals('ffa601', $image->pickColor(25, 25)->toHex()); $result = $image->invert(); $this->assertInstanceOf(ImageInterface::class, $result); $this->assertEquals('ff510f', $image->pickColor(0, 0)->toHex()); $this->assertEquals('0059fe', $image->pickColor(25, 25)->toHex()); } public function testPixelate(): void { $image = $this->readTestImage('trim.png'); $this->assertEquals('00aef0', $image->pickColor(0, 0)->toHex()); $this->assertEquals('00aef0', $image->pickColor(14, 14)->toHex()); $result = $image->pixelate(10); $this->assertInstanceOf(ImageInterface::class, $result); [$r, $g, $b] = $image->pickColor(0, 0)->toArray(); $this->assertEquals(0, $r); $this->assertEquals(174, $g); $this->assertEquals(240, $b); [$r, $g, $b] = $image->pickColor(14, 14)->toArray(); $this->assertEquals(107, $r); $this->assertEquals(171, $g); $this->assertEquals(140, $b); } public function testGreyscale(): void { $image = $this->readTestImage('trim.png'); $this->assertFalse($image->pickColor(0, 0)->isGreyscale()); $result = $image->greyscale(); $this->assertInstanceOf(ImageInterface::class, $result); $this->assertTrue($image->pickColor(0, 0)->isGreyscale()); } public function testBrightness(): void { $image = $this->readTestImage('trim.png'); $this->assertEquals('00aef0', $image->pickColor(14, 14)->toHex()); $result = $image->brightness(30); $this->assertInstanceOf(ImageInterface::class, $result); $this->assertEquals('39c9ff', $image->pickColor(14, 14)->toHex()); } public function testDebugInfo(): void { $info = $this->readTestImage('trim.png')->__debugInfo(); $this->assertArrayHasKey('width', $info); $this->assertArrayHasKey('height', $info); } } ================================================ FILE: tests/Unit/Drivers/Imagick/Modifiers/BlurModifierTest.php ================================================ readTestImage('trim.png'); $this->assertEquals('00aef0', $image->pickColor(14, 14)->toHex()); $image->modify(new BlurModifier(30)); $this->assertEquals('42acb2', $image->pickColor(14, 14)->toHex()); } } ================================================ FILE: tests/Unit/Drivers/Imagick/Modifiers/BrightnessModifierTest.php ================================================ readTestImage('trim.png'); $this->assertEquals('00aef0', $image->pickColor(14, 14)->toHex()); $image->modify(new BrightnessModifier(30)); $this->assertEquals('39c9ff', $image->pickColor(14, 14)->toHex()); } } ================================================ FILE: tests/Unit/Drivers/Imagick/Modifiers/ColorizeModifierTest.php ================================================ readTestImage('tile.png'); $image = $image->modify(new ColorizeModifier(100, -100, -100)); $this->assertColor(251, 0, 0, 255, $image->pickColor(5, 5)); $this->assertColor(239, 0, 0, 255, $image->pickColor(15, 15)); } } ================================================ FILE: tests/Unit/Drivers/Imagick/Modifiers/ContainModifierTest.php ================================================ readTestImage('blocks.png'); $this->assertEquals(640, $image->width()); $this->assertEquals(480, $image->height()); $result = $image->modify(new ContainModifier(200, 100, 'ff0')); $this->assertEquals(200, $image->width()); $this->assertEquals(100, $image->height()); $this->assertColor(255, 255, 0, 255, $image->pickColor(0, 0)); $this->assertColor(0, 0, 0, 0, $image->pickColor(140, 10)); $this->assertColor(255, 255, 0, 255, $image->pickColor(175, 10)); $this->assertEquals(200, $result->width()); $this->assertEquals(100, $result->height()); $this->assertColor(255, 255, 0, 255, $result->pickColor(0, 0)); $this->assertColor(0, 0, 0, 0, $result->pickColor(140, 10)); $this->assertColor(255, 255, 0, 255, $result->pickColor(175, 10)); } } ================================================ FILE: tests/Unit/Drivers/Imagick/Modifiers/ContrastModifierTest.php ================================================ readTestImage('trim.png'); $this->assertEquals('00aef0', $image->pickColor(14, 14)->toHex()); $image->modify(new ContrastModifier(30)); $this->assertEquals('00fcff', $image->pickColor(14, 14)->toHex()); } } ================================================ FILE: tests/Unit/Drivers/Imagick/Modifiers/CoverDownModifierTest.php ================================================ readTestImage('blocks.png'); $this->assertEquals(640, $image->width()); $this->assertEquals(480, $image->height()); $image->modify(new CoverDownModifier(100, 100, 'center')); $this->assertEquals(100, $image->width()); $this->assertEquals(100, $image->height()); $this->assertColor(255, 0, 0, 255, $image->pickColor(90, 90)); $this->assertColor(0, 255, 0, 255, $image->pickColor(65, 70)); $this->assertColor(0, 0, 255, 255, $image->pickColor(70, 52)); $this->assertTransparency($image->pickColor(90, 30)); } public function testModifyOddSize(): void { $image = $this->createTestImage(375, 250); $image->modify(new CoverDownModifier(240, 90, 'center')); $this->assertEquals(240, $image->width()); $this->assertEquals(90, $image->height()); } } ================================================ FILE: tests/Unit/Drivers/Imagick/Modifiers/CoverModifierTest.php ================================================ readTestImage('blocks.png'); $this->assertEquals(640, $image->width()); $this->assertEquals(480, $image->height()); $image->modify(new CoverModifier(100, 100, 'center')); $this->assertEquals(100, $image->width()); $this->assertEquals(100, $image->height()); $this->assertColor(255, 0, 0, 255, $image->pickColor(90, 90)); $this->assertColor(0, 255, 0, 255, $image->pickColor(65, 70)); $this->assertColor(0, 0, 255, 255, $image->pickColor(70, 52)); $this->assertTransparency($image->pickColor(90, 30)); } public function testModifyOddSize(): void { $image = $this->createTestImage(375, 250); $image->modify(new CoverModifier(240, 90, 'center')); $this->assertEquals(240, $image->width()); $this->assertEquals(90, $image->height()); } } ================================================ FILE: tests/Unit/Drivers/Imagick/Modifiers/CropModifierTest.php ================================================ readTestImage('blocks.png'); $image = $image->modify(new CropModifier(200, 200, 0, 0, 'ffffff', 'bottom-right')); $this->assertEquals(200, $image->width()); $this->assertEquals(200, $image->height()); $this->assertColor(255, 0, 0, 255, $image->pickColor(5, 5)); $this->assertColor(255, 0, 0, 255, $image->pickColor(100, 100)); $this->assertColor(255, 0, 0, 255, $image->pickColor(190, 190)); } public function testModifyExtend(): void { $image = $this->readTestImage('blocks.png'); $image = $image->modify(new CropModifier(800, 100, -10, -10, 'ff0000', 'top-left')); $this->assertEquals(800, $image->width()); $this->assertEquals(100, $image->height()); $this->assertColor(255, 0, 0, 255, $image->pickColor(9, 9)); $this->assertColor(0, 0, 255, 255, $image->pickColor(16, 16)); $this->assertColor(0, 0, 255, 255, $image->pickColor(445, 16)); $this->assertTransparency($image->pickColor(460, 16)); } public function testModifySinglePixel(): void { $image = $this->createTestImage(1, 1); $this->assertEquals(1, $image->width()); $this->assertEquals(1, $image->height()); $image->modify(new CropModifier(3, 3, 0, 0, 'ff0', 'center')); $this->assertEquals(3, $image->width()); $this->assertEquals(3, $image->height()); $this->assertColor(255, 255, 0, 255, $image->pickColor(0, 0)); $this->assertColor(255, 0, 0, 255, $image->pickColor(1, 1)); $this->assertColor(255, 255, 0, 255, $image->pickColor(2, 2)); } public function testModifyKeepsColorspace(): void { $image = $this->readTestImage('cmyk.jpg'); $this->assertInstanceOf(Colorspace::class, $image->colorspace()); $image = $image->modify(new CropModifier(800, 100, -10, -10, 'ff0000')); $this->assertInstanceOf(Colorspace::class, $image->colorspace()); } public function testModifyKeepsResolution(): void { $image = $this->readTestImage('300dpi.png'); $this->assertEquals(300, round($image->resolution()->perInch()->x())); $image = $image->modify(new CropModifier(800, 100, -10, -10, 'ff0000')); $this->assertEquals(300, round($image->resolution()->perInch()->x())); } public function testHalfTransparent(): void { $image = $this->createTestImage(16, 16); $image->modify(new CropModifier(32, 32, 0, 0, '00f5', 'center')); $this->assertEquals(32, $image->width()); $this->assertEquals(32, $image->height()); $this->assertColor(0, 0, 255, 77, $image->pickColor(5, 5)); $this->assertColor(0, 0, 255, 77, $image->pickColor(16, 5)); $this->assertColor(0, 0, 255, 77, $image->pickColor(30, 5)); $this->assertColor(0, 0, 255, 77, $image->pickColor(5, 16)); $this->assertColor(255, 0, 0, 255, $image->pickColor(16, 16)); $this->assertColor(0, 0, 255, 77, $image->pickColor(30, 16)); $this->assertColor(0, 0, 255, 77, $image->pickColor(5, 30)); $this->assertColor(0, 0, 255, 77, $image->pickColor(16, 30)); $this->assertColor(0, 0, 255, 77, $image->pickColor(30, 30)); } public function testMergeTransparentBackgrounds(): void { $image = $this->createTestImage(1, 1)->fill('f00'); $this->assertEquals(1, $image->width()); $this->assertEquals(1, $image->height()); $image->modify(new CropModifier(3, 3, 0, 0, '00f7', 'center')); $this->assertEquals(3, $image->width()); $this->assertEquals(3, $image->height()); $this->assertColor(0, 0, 255, 127, $image->pickColor(0, 0), 1); $this->assertColor(255, 0, 0, 255, $image->pickColor(1, 1)); $this->assertColor(0, 0, 255, 127, $image->pickColor(2, 2), 1); } } ================================================ FILE: tests/Unit/Drivers/Imagick/Modifiers/DrawBezierModifierTest.php ================================================ readTestImage('trim.png'); $this->assertEquals('00aef0', $image->pickColor(14, 14)->toHex()); $drawable = new Bezier([ new Point(0, 0), new Point(15, 0), new Point(15, 15), new Point(0, 15) ]); $drawable->setBackgroundColor('b53717'); $image->modify(new DrawBezierModifier($drawable)); $this->assertEquals('b53717', $image->pickColor(5, 5)->toHex()); } } ================================================ FILE: tests/Unit/Drivers/Imagick/Modifiers/DrawEllipseModifierTest.php ================================================ readTestImage('trim.png'); $this->assertEquals('00aef0', $image->pickColor(14, 14)->toHex()); $drawable = new Ellipse(10, 10, new Point(14, 14)); $drawable->setBackgroundColor('b53717'); $image->modify(new DrawEllipseModifier($drawable)); $this->assertEquals('b53717', $image->pickColor(14, 14)->toHex()); } } ================================================ FILE: tests/Unit/Drivers/Imagick/Modifiers/DrawLineModifierTest.php ================================================ readTestImage('trim.png'); $this->assertEquals('00aef0', $image->pickColor(14, 14)->toHex()); $line = new Line(new Point(0, 0), new Point(10, 0), 4); $line->setBackgroundColor('b53517'); $image->modify(new DrawLineModifier($line)); $this->assertEquals('b53517', $image->pickColor(0, 0)->toHex()); } public function testApplyTransparent(): void { $image = $this->createTestImage(10, 10)->fill('ff5500'); $this->assertColor(255, 85, 0, 255, $image->pickColor(5, 5)); $line = new Line(new Point(0, 5), new Point(10, 5), 4); $line->setBackgroundColor('fff4'); $image->modify(new DrawLineModifier($line)); $this->assertColor(255, 136, 77, 255, $image->pickColor(5, 5)); } } ================================================ FILE: tests/Unit/Drivers/Imagick/Modifiers/DrawPixelModifierTest.php ================================================ readTestImage('trim.png'); $this->assertEquals('00aef0', $image->pickColor(14, 14)->toHex()); $image->modify(new DrawPixelModifier(new Point(14, 14), 'ffffff')); $this->assertEquals('ffffff', $image->pickColor(14, 14)->toHex()); } } ================================================ FILE: tests/Unit/Drivers/Imagick/Modifiers/DrawPolygonModifierTest.php ================================================ readTestImage('trim.png'); $this->assertEquals('00aef0', $image->pickColor(14, 14)->toHex()); $drawable = new Polygon([new Point(0, 0), new Point(15, 15), new Point(20, 20)]); $drawable->setBackgroundColor('b53717'); $image->modify(new DrawPolygonModifier($drawable)); $this->assertEquals('b53717', $image->pickColor(14, 14)->toHex()); } } ================================================ FILE: tests/Unit/Drivers/Imagick/Modifiers/DrawRectangleModifierTest.php ================================================ readTestImage('trim.png'); $this->assertEquals('00aef0', $image->pickColor(14, 14)->toHex()); $rectangle = new Rectangle(300, 200, new Point(14, 14)); $rectangle->setBackgroundColor('ffffff'); $image->modify(new DrawRectangleModifier($rectangle)); $this->assertEquals('ffffff', $image->pickColor(14, 14)->toHex()); } } ================================================ FILE: tests/Unit/Drivers/Imagick/Modifiers/FillModifierTest.php ================================================ readTestImage('blocks.png'); $this->assertEquals('0000ff', $image->pickColor(420, 270)->toHex()); $this->assertEquals('ff0000', $image->pickColor(540, 400)->toHex()); $image->modify(new FillModifier(new Color(204, 204, 204), new Point(540, 400))); $this->assertEquals('0000ff', $image->pickColor(420, 270)->toHex()); $this->assertEquals('cccccc', $image->pickColor(540, 400)->toHex()); } public function testFillAllColor(): void { $image = $this->readTestImage('blocks.png'); $this->assertEquals('0000ff', $image->pickColor(420, 270)->toHex()); $this->assertEquals('ff0000', $image->pickColor(540, 400)->toHex()); $image->modify(new FillModifier(new Color(204, 204, 204))); $this->assertEquals('cccccc', $image->pickColor(420, 270)->toHex()); $this->assertEquals('cccccc', $image->pickColor(540, 400)->toHex()); } } ================================================ FILE: tests/Unit/Drivers/Imagick/Modifiers/FlipFlopModifierTest.php ================================================ readTestImage('tile.png'); $this->assertEquals('b4e000', $image->pickColor(0, 0)->toHex()); $image->modify(new FlipModifier()); $this->assertEquals('00000000', $image->pickColor(0, 0)->toHex()); } public function testFlopImage(): void { $image = $this->readTestImage('tile.png'); $this->assertEquals('b4e000', $image->pickColor(0, 0)->toHex()); $image->modify(new FlopModifier()); $this->assertEquals('00000000', $image->pickColor(0, 0)->toHex()); } } ================================================ FILE: tests/Unit/Drivers/Imagick/Modifiers/GammaModifierTest.php ================================================ readTestImage('trim.png'); $this->assertEquals('00aef0', $image->pickColor(0, 0)->toHex()); $image->modify(new GammaModifier(2.1)); $this->assertEquals('00d5f8', $image->pickColor(0, 0)->toHex()); } } ================================================ FILE: tests/Unit/Drivers/Imagick/Modifiers/GreyscaleModifierTest.php ================================================ readTestImage('trim.png'); $this->assertFalse($image->pickColor(0, 0)->isGreyscale()); $image->modify(new GreyscaleModifier()); $this->assertTrue($image->pickColor(0, 0)->isGreyscale()); } } ================================================ FILE: tests/Unit/Drivers/Imagick/Modifiers/InvertModifierTest.php ================================================ readTestImage('trim.png'); $this->assertEquals('00aef0', $image->pickColor(0, 0)->toHex()); $this->assertEquals('ffa601', $image->pickColor(25, 25)->toHex()); $image->modify(new InvertModifier()); $this->assertEquals('ff510f', $image->pickColor(0, 0)->toHex()); $this->assertEquals('0059fe', $image->pickColor(25, 25)->toHex()); } } ================================================ FILE: tests/Unit/Drivers/Imagick/Modifiers/PadModifierTest.php ================================================ readTestImage('blue.gif'); $this->assertEquals(16, $image->width()); $this->assertEquals(16, $image->height()); $image->modify(new PadModifier(30, 20, 'f00')); $this->assertEquals(30, $image->width()); $this->assertEquals(20, $image->height()); $this->assertColor(255, 0, 0, 255, $image->pickColor(0, 0)); $this->assertColor(255, 0, 0, 255, $image->pickColor(0, 19)); $this->assertColor(255, 0, 0, 255, $image->pickColor(29, 0)); $this->assertColor(255, 0, 0, 255, $image->pickColor(29, 19)); $this->assertColor(255, 0, 0, 255, $image->pickColor(6, 2)); $this->assertColor(255, 0, 0, 255, $image->pickColor(7, 1)); $this->assertColor(255, 0, 0, 255, $image->pickColor(6, 17)); $this->assertColor(255, 0, 0, 255, $image->pickColor(7, 18)); $this->assertColor(255, 0, 0, 255, $image->pickColor(23, 1)); $this->assertColor(255, 0, 0, 255, $image->pickColor(23, 2)); $this->assertColor(255, 0, 0, 255, $image->pickColor(23, 17)); $this->assertColor(255, 0, 0, 255, $image->pickColor(23, 18)); $this->assertColor(100, 100, 255, 255, $image->pickColor(7, 2)); $this->assertColor(100, 100, 255, 255, $image->pickColor(22, 2)); $this->assertColor(100, 100, 255, 255, $image->pickColor(7, 17)); $this->assertColor(100, 100, 255, 255, $image->pickColor(22, 17)); } } ================================================ FILE: tests/Unit/Drivers/Imagick/Modifiers/PixelateModifierTest.php ================================================ readTestImage('trim.png'); $this->assertEquals('00aef0', $image->pickColor(0, 0)->toHex()); $this->assertEquals('00aef0', $image->pickColor(14, 14)->toHex()); $image->modify(new PixelateModifier(10)); [$r, $g, $b] = $image->pickColor(0, 0)->toArray(); $this->assertEquals(0, $r); $this->assertEquals(174, $g); $this->assertEquals(240, $b); [$r, $g, $b] = $image->pickColor(14, 14)->toArray(); $this->assertEquals(107, $r); $this->assertEquals(171, $g); $this->assertEquals(140, $b); } } ================================================ FILE: tests/Unit/Drivers/Imagick/Modifiers/PlaceModifierTest.php ================================================ readTestImage('test.jpg'); $this->assertEquals('febc44', $image->pickColor(300, 25)->toHex()); $image->modify(new PlaceModifier($this->getTestResourcePath('circle.png'), 'top-right', 0, 0)); $this->assertEquals('33260e', $image->pickColor(300, 25)->toHex()); } public function testColorChangeOpacityPng(): void { $image = $this->readTestImage('test.jpg'); $this->assertEquals('febc44', $image->pickColor(300, 25)->toHex()); $image->modify(new PlaceModifier($this->getTestResourcePath('circle.png'), 'top-right', 0, 0, 50)); $this->assertColor(152, 112, 40, 255, $image->pickColor(300, 25), tolerance: 1); $this->assertColor(255, 202, 107, 255, $image->pickColor(274, 5), tolerance: 1); } public function testColorChangeOpacityJpeg(): void { $image = $this->createTestImage(16, 16)->fill('0000ff'); $this->assertEquals('0000ff', $image->pickColor(10, 10)->toHex()); $image->modify(new PlaceModifier($this->getTestResourcePath('exif.jpg'), opacity: 50)); $this->assertColor(127, 83, 127, 255, $image->pickColor(10, 10), tolerance: 1); } } ================================================ FILE: tests/Unit/Drivers/Imagick/Modifiers/QuantizeColorsModifierTest.php ================================================ readTestImage('gradient.bmp'); $this->assertEquals(15, $image->core()->native()->getImageColors()); $image->modify(new QuantizeColorsModifier(4)); $this->assertEquals(4, $image->core()->native()->getImageColors()); } public function testNoColorReduction(): void { $image = $this->readTestImage('gradient.bmp'); $this->assertEquals(15, $image->core()->native()->getImageColors()); $image->modify(new QuantizeColorsModifier(150)); $this->assertEquals(15, $image->core()->native()->getImageColors()); } public function testInvalidColorInput(): void { $image = $this->readTestImage('gradient.bmp'); $this->expectException(InputException::class); $image->modify(new QuantizeColorsModifier(0)); } public function testVerifyColorValueAfterQuantization(): void { $image = $this->createTestImage(3, 2)->fill('f00'); $image->modify(new QuantizeColorsModifier(1)); $this->assertColor(255, 0, 0, 255, $image->pickColor(1, 1)); } } ================================================ FILE: tests/Unit/Drivers/Imagick/Modifiers/RemoveAnimationModifierTest.php ================================================ readTestImage('animation.gif'); $this->assertEquals(8, count($image)); $result = $image->modify(new RemoveAnimationModifier(2)); $this->assertEquals(1, count($image)); $this->assertEquals(1, count($result)); } public function testApplyPercent(): void { $image = $this->readTestImage('animation.gif'); $this->assertEquals(8, count($image)); $result = $image->modify(new RemoveAnimationModifier('20%')); $this->assertEquals(1, count($image)); $this->assertEquals(1, count($result)); } public function testApplyNonAnimated(): void { $image = $this->readTestImage('test.jpg'); $this->assertEquals(1, count($image)); $result = $image->modify(new RemoveAnimationModifier()); $this->assertEquals(1, count($image)); $this->assertEquals(1, count($result)); } public function testApplyInvalid(): void { $image = $this->readTestImage('animation.gif'); $this->expectException(InputException::class); $image->modify(new RemoveAnimationModifier('test')); } } ================================================ FILE: tests/Unit/Drivers/Imagick/Modifiers/ResizeCanvasModifierTest.php ================================================ createTestImage(1, 1); $this->assertEquals(1, $image->width()); $this->assertEquals(1, $image->height()); $image->modify(new ResizeCanvasModifier(3, 3, 'ff0', 'center')); $this->assertEquals(3, $image->width()); $this->assertEquals(3, $image->height()); $this->assertColor(255, 255, 0, 255, $image->pickColor(0, 0)); $this->assertColor(255, 0, 0, 255, $image->pickColor(1, 1)); $this->assertColor(255, 255, 0, 255, $image->pickColor(2, 2)); } public function testModifyWithTransparency(): void { $image = $this->readTestImage('tile.png'); $this->assertEquals(16, $image->width()); $this->assertEquals(16, $image->height()); $image->modify(new ResizeCanvasModifier(18, 18, 'ff0', 'center')); $this->assertEquals(18, $image->width()); $this->assertEquals(18, $image->height()); $this->assertColor(255, 255, 0, 255, $image->pickColor(0, 0)); $this->assertColor(180, 224, 0, 255, $image->pickColor(1, 1)); $this->assertColor(180, 224, 0, 255, $image->pickColor(2, 2)); $this->assertColor(255, 255, 0, 255, $image->pickColor(17, 17)); $this->assertTransparency($image->pickColor(12, 1)); $image = $this->createTestImage(16, 16); $image->modify(new ResizeCanvasModifier(32, 32, '00f5', 'center')); $this->assertEquals(32, $image->width()); $this->assertEquals(32, $image->height()); $this->assertColor(0, 0, 255, 77, $image->pickColor(5, 5)); $this->assertColor(0, 0, 255, 77, $image->pickColor(16, 5)); $this->assertColor(0, 0, 255, 77, $image->pickColor(30, 5)); $this->assertColor(0, 0, 255, 77, $image->pickColor(5, 16)); $this->assertColor(255, 0, 0, 255, $image->pickColor(16, 16)); $this->assertColor(0, 0, 255, 77, $image->pickColor(30, 16)); $this->assertColor(0, 0, 255, 77, $image->pickColor(5, 30)); $this->assertColor(0, 0, 255, 77, $image->pickColor(16, 30)); $this->assertColor(0, 0, 255, 77, $image->pickColor(30, 30)); } public function testModifyEdge(): void { $image = $this->createTestImage(1, 1); $this->assertColor(255, 0, 0, 255, $image->pickColor(0, 0)); $image->modify(new ResizeCanvasModifier(null, 2, 'ff0', 'bottom')); $this->assertEquals(1, $image->width()); $this->assertEquals(2, $image->height()); $this->assertColor(255, 255, 0, 255, $image->pickColor(0, 0)); $this->assertColor(255, 0, 0, 255, $image->pickColor(0, 1)); } } ================================================ FILE: tests/Unit/Drivers/Imagick/Modifiers/ResizeCanvasRelativeModifierTest.php ================================================ createTestImage(1, 1); $this->assertEquals(1, $image->width()); $this->assertEquals(1, $image->height()); $image->modify(new ResizeCanvasRelativeModifier(2, 2, 'ff0', 'center')); $this->assertEquals(3, $image->width()); $this->assertEquals(3, $image->height()); $this->assertColor(255, 255, 0, 255, $image->pickColor(0, 0)); $this->assertColor(255, 0, 0, 255, $image->pickColor(1, 1)); $this->assertColor(255, 255, 0, 255, $image->pickColor(2, 2)); } public function testModifyWithTransparency(): void { $image = $this->readTestImage('tile.png'); $this->assertEquals(16, $image->width()); $this->assertEquals(16, $image->height()); $image->modify(new ResizeCanvasRelativeModifier(2, 2, 'ff0', 'center')); $this->assertEquals(18, $image->width()); $this->assertEquals(18, $image->height()); $this->assertColor(255, 255, 0, 255, $image->pickColor(0, 0)); $this->assertColor(180, 224, 0, 255, $image->pickColor(1, 1)); $this->assertColor(180, 224, 0, 255, $image->pickColor(2, 2)); $this->assertColor(255, 255, 0, 255, $image->pickColor(17, 17)); $this->assertTransparency($image->pickColor(12, 1)); } } ================================================ FILE: tests/Unit/Drivers/Imagick/Modifiers/ResizeModifierTest.php ================================================ readTestImage('blocks.png'); $this->assertEquals(640, $image->width()); $this->assertEquals(480, $image->height()); $image->modify(new ResizeModifier(200, 100)); $this->assertEquals(200, $image->width()); $this->assertEquals(100, $image->height()); $this->assertColor(255, 0, 0, 255, $image->pickColor(150, 70)); } } ================================================ FILE: tests/Unit/Drivers/Imagick/Modifiers/ResolutionModifierTest.php ================================================ readTestImage('test.jpg'); $this->assertEquals(72.0, $image->resolution()->x()); $this->assertEquals(72.0, $image->resolution()->y()); $image->modify(new ResolutionModifier(1, 2)); $this->assertEquals(1.0, $image->resolution()->x()); $this->assertEquals(2.0, $image->resolution()->y()); } } ================================================ FILE: tests/Unit/Drivers/Imagick/Modifiers/RotateModifierTest.php ================================================ readTestImage('test.jpg'); $this->assertEquals(320, $image->width()); $this->assertEquals(240, $image->height()); $image->modify(new RotateModifier(90, 'fff')); $this->assertEquals(240, $image->width()); $this->assertEquals(320, $image->height()); } } ================================================ FILE: tests/Unit/Drivers/Imagick/Modifiers/SharpenModifierTest.php ================================================ readTestImage('trim.png'); $this->assertEquals('60ab96', $image->pickColor(15, 14)->toHex()); $image->modify(new SharpenModifier(10)); $this->assertEquals('4faca6', $image->pickColor(15, 14)->toHex()); } } ================================================ FILE: tests/Unit/Drivers/Imagick/Modifiers/StripMetaModifierTest.php ================================================ readTestImage('exif.jpg'); $this->assertEquals('Oliver Vogel', $image->exif('IFD0.Artist')); $image->modify(new StripMetaModifier()); $this->assertNull($image->exif('IFD0.Artist')); $result = $image->toJpeg(); $this->assertEmpty(exif_read_data($result->toFilePointer())['IFD0.Artist'] ?? null); } } ================================================ FILE: tests/Unit/Drivers/Imagick/Modifiers/TextModifierTest.php ================================================ setColor('ff0055'); $modifier = new class ('test', new Point(), $font) extends TextModifier { public function test(): ColorInterface { return $this->textColor(); } }; $modifier->setDriver(new Driver()); $this->assertInstanceOf(ColorInterface::class, $modifier->test()); } } ================================================ FILE: tests/Unit/Drivers/Imagick/Modifiers/TrimModifierTest.php ================================================ readTestImage('trim.png'); $this->assertEquals(50, $image->width()); $this->assertEquals(50, $image->height()); $image->modify(new TrimModifier()); $this->assertEquals(28, $image->width()); $this->assertEquals(28, $image->height()); } public function testTrimGradient(): void { $image = $this->readTestImage('radial.png'); $this->assertEquals(50, $image->width()); $this->assertEquals(50, $image->height()); $image->modify(new TrimModifier(50)); $this->assertEquals(29, $image->width()); $this->assertEquals(29, $image->height()); } public function testTrimHighTolerance(): void { $image = $this->readTestImage('trim.png'); $this->assertEquals(50, $image->width()); $this->assertEquals(50, $image->height()); $image->modify(new TrimModifier(1000000)); $this->assertEquals(1, $image->width()); $this->assertEquals(1, $image->height()); $this->assertColor(255, 255, 255, 0, $image->pickColor(0, 0)); } public function testTrimAnimated(): void { $image = $this->readTestImage('animation.gif'); $this->expectException(NotSupportedException::class); $image->modify(new TrimModifier()); } } ================================================ FILE: tests/Unit/Drivers/SpecializableAnalyzerTest.php ================================================ makePartial(); $image = Mockery::mock(ImageInterface::class); $image->shouldReceive('analyze')->andReturn('test'); $result = $analyzer->analyze($image); $this->assertEquals('test', $result); } } ================================================ FILE: tests/Unit/Drivers/SpecializableDecoderTest.php ================================================ makePartial(); $this->expectException(DecoderException::class); $decoder->decode(null); } } ================================================ FILE: tests/Unit/Drivers/SpecializableModifierTest.php ================================================ makePartial(); $image = Mockery::mock(ImageInterface::class); $image->shouldReceive('modify')->andReturn($image); $result = $modifier->apply($image); $this->assertInstanceOf(ImageInterface::class, $result); } } ================================================ FILE: tests/Unit/EncodedImageTest.php ================================================ assertInstanceOf(EncodedImage::class, $image); } public function testSave(): void { $image = new EncodedImage('foo'); $path = __DIR__ . '/foo.tmp'; $this->assertFalse(file_exists($path)); $image->save($path); $this->assertTrue(file_exists($path)); $this->assertEquals('foo', file_get_contents($path)); unlink($path); } public function testToDataUri(): void { $image = new EncodedImage('foo'); $this->assertEquals('data:application/octet-stream;base64,Zm9v', $image->toDataUri()); } public function testToString(): void { $image = new EncodedImage('foo'); $this->assertEquals('foo', (string) $image); } public function testMediaType(): void { $image = new EncodedImage('foo'); $this->assertEquals('application/octet-stream', $image->mediaType()); $image = new EncodedImage($this->getTestResourceData(), 'image/jpeg'); $this->assertEquals('image/jpeg', $image->mediaType()); } public function testMimetype(): void { $image = new EncodedImage('foo'); $this->assertEquals('application/octet-stream', $image->mimetype()); $image = new EncodedImage($this->getTestResourceData(), 'image/jpeg'); $this->assertEquals('image/jpeg', $image->mimetype()); } public function testDebugInfo(): void { $info = (new EncodedImage('foo', 'image/png'))->__debugInfo(); $this->assertEquals('image/png', $info['mediaType']); $this->assertEquals(3, $info['size']); } } ================================================ FILE: tests/Unit/Encoders/FileExtensionEncoderTest.php ================================================ */ private function testEncoder(string|FileExtension $extension, array $options = []): EncoderInterface { $encoder = new class ($extension, ...$options) extends FileExtensionEncoder { public function __construct(string|FileExtension $extension, mixed ...$options) { parent::__construct($extension, ...$options); } public function test(string|FileExtension $extension): EncoderInterface { return $this->encoderByFileExtension($extension); } }; return $encoder->test($extension); } #[DataProvider('targetEncoderProvider')] public function testEncoderByFileExtensionString( string|FileExtension $fileExtension, string $targetEncoderClassname, ): void { $this->assertInstanceOf( $targetEncoderClassname, $this->testEncoder($fileExtension), ); } public static function targetEncoderProvider(): Generator { yield ['webp', WebpEncoder::class]; yield ['avif', AvifEncoder::class]; yield ['jpeg', JpegEncoder::class]; yield ['jpg', JpegEncoder::class]; yield ['bmp', BmpEncoder::class]; yield ['gif', GifEncoder::class]; yield ['png', PngEncoder::class]; yield ['tiff', TiffEncoder::class]; yield ['tif', TiffEncoder::class]; yield ['jp2', Jpeg2000Encoder::class]; yield ['heic', HeicEncoder::class]; yield ['WEBP', WebpEncoder::class]; yield ['AVIF', AvifEncoder::class]; yield ['JPEG', JpegEncoder::class]; yield ['JPG', JpegEncoder::class]; yield ['BMP', BmpEncoder::class]; yield ['GIF', GifEncoder::class]; yield ['PNG', PngEncoder::class]; yield ['TIFF', TiffEncoder::class]; yield ['TIF', TiffEncoder::class]; yield ['JP2', Jpeg2000Encoder::class]; yield ['HEIC', HeicEncoder::class]; yield [FileExtension::WEBP, WebpEncoder::class]; yield [FileExtension::AVIF, AvifEncoder::class]; yield [FileExtension::JPG, JpegEncoder::class]; yield [FileExtension::BMP, BmpEncoder::class]; yield [FileExtension::GIF, GifEncoder::class]; yield [FileExtension::PNG, PngEncoder::class]; yield [FileExtension::TIF, TiffEncoder::class]; yield [FileExtension::TIFF, TiffEncoder::class]; yield [FileExtension::JP2, Jpeg2000Encoder::class]; yield [FileExtension::HEIC, HeicEncoder::class]; } public function testArgumentsNotSupportedByTargetEncoder(): void { $encoder = $this->testEncoder( 'png', [ 'interlaced' => true, // is not ignored 'quality' => 10, // is ignored because png encoder has no quality argument ], ); $this->assertInstanceOf(PngEncoder::class, $encoder); $this->assertTrue($encoder->interlaced); } public function testEncoderByFileExtensionUnknown(): void { $this->expectException(EncoderException::class); $this->testEncoder('test'); } } ================================================ FILE: tests/Unit/Encoders/MediaTypeEncoderTest.php ================================================ */ private function testEncoder(string|MediaType $mediaType, array $options = []): EncoderInterface { $encoder = new class ($mediaType, ...$options) extends MediaTypeEncoder { public function __construct(string|MediaType $mediaType, mixed ...$options) { parent::__construct($mediaType, ...$options); } public function test(string|MediaType $mediaType): EncoderInterface { return $this->encoderByMediaType($mediaType); } }; return $encoder->test($mediaType); } #[DataProvider('targetEncoderProvider')] public function testEncoderByMediaType( string|MediaType $mediaType, string $targetEncoderClassname, ): void { $this->assertInstanceOf( $targetEncoderClassname, $this->testEncoder($mediaType) ); } public static function targetEncoderProvider(): Generator { yield ['image/webp', WebpEncoder::class]; yield ['image/avif', AvifEncoder::class]; yield ['image/jpeg', JpegEncoder::class]; yield ['image/bmp', BmpEncoder::class]; yield ['image/gif', GifEncoder::class]; yield ['image/png', PngEncoder::class]; yield ['image/png', PngEncoder::class]; yield ['image/tiff', TiffEncoder::class]; yield ['image/jp2', Jpeg2000Encoder::class]; yield ['image/heic', HeicEncoder::class]; yield [MediaType::IMAGE_WEBP, WebpEncoder::class]; yield [MediaType::IMAGE_AVIF, AvifEncoder::class]; yield [MediaType::IMAGE_JPEG, JpegEncoder::class]; yield [MediaType::IMAGE_BMP, BmpEncoder::class]; yield [MediaType::IMAGE_GIF, GifEncoder::class]; yield [MediaType::IMAGE_PNG, PngEncoder::class]; yield [MediaType::IMAGE_TIFF, TiffEncoder::class]; yield [MediaType::IMAGE_JP2, Jpeg2000Encoder::class]; yield [MediaType::IMAGE_HEIC, HeicEncoder::class]; yield [MediaType::IMAGE_HEIF, HeicEncoder::class]; } public function testArgumentsNotSupportedByTargetEncoder(): void { $encoder = $this->testEncoder( 'image/png', [ 'interlaced' => true, // is not ignored 'quality' => 10, // is ignored because png encoder has no quality argument ], ); $this->assertInstanceOf(PngEncoder::class, $encoder); $this->assertTrue($encoder->interlaced); } public function testEncoderByFileExtensionUnknown(): void { $this->expectException(EncoderException::class); $this->testEncoder('test'); } } ================================================ FILE: tests/Unit/FileExtensionTest.php ================================================ assertEquals(FileExtension::JPG, FileExtension::create(MediaType::IMAGE_JPEG)); $this->assertEquals(FileExtension::JPG, FileExtension::create(Format::JPEG)); $this->assertEquals(FileExtension::JPG, FileExtension::create(FileExtension::JPG)); $this->assertEquals(FileExtension::JPG, FileExtension::create('jpg')); $this->assertEquals(FileExtension::JPEG, FileExtension::create('jpeg')); $this->assertEquals(FileExtension::JPG, FileExtension::create('image/jpeg')); $this->assertEquals(FileExtension::JPG, FileExtension::create('JPG')); $this->assertEquals(FileExtension::JPEG, FileExtension::create('JPEG')); $this->assertEquals(FileExtension::JPG, FileExtension::create('IMAGE/JPEG')); } public function testCreateUnknown(): void { $this->expectException(NotSupportedException::class); FileExtension::create('foo'); } public function testTryCreate(): void { $this->assertEquals(FileExtension::JPG, FileExtension::tryCreate(MediaType::IMAGE_JPEG)); $this->assertEquals(FileExtension::JPG, FileExtension::tryCreate(Format::JPEG)); $this->assertEquals(FileExtension::JPG, FileExtension::tryCreate(FileExtension::JPG)); $this->assertEquals(FileExtension::JPG, FileExtension::tryCreate('jpg')); $this->assertEquals(FileExtension::JPEG, FileExtension::tryCreate('jpeg')); $this->assertEquals(FileExtension::JPG, FileExtension::tryCreate('image/jpeg')); $this->assertNull(FileExtension::tryCreate('no-format')); } public function testFormatJpeg(): void { $ext = FileExtension::JPEG; $this->assertEquals(Format::JPEG, $ext->format()); $ext = FileExtension::JPG; $this->assertEquals(Format::JPEG, $ext->format()); } public function testFormatWebp(): void { $ext = FileExtension::WEBP; $this->assertEquals(Format::WEBP, $ext->format()); } public function testFormatGif(): void { $ext = FileExtension::GIF; $this->assertEquals(Format::GIF, $ext->format()); } public function testFormatPng(): void { $ext = FileExtension::PNG; $this->assertEquals(Format::PNG, $ext->format()); } public function testFormatAvif(): void { $ext = FileExtension::AVIF; $this->assertEquals(Format::AVIF, $ext->format()); } public function testFormatBmp(): void { $ext = FileExtension::BMP; $this->assertEquals(Format::BMP, $ext->format()); } public function testFormatTiff(): void { $ext = FileExtension::TIFF; $this->assertEquals(Format::TIFF, $ext->format()); $ext = FileExtension::TIF; $this->assertEquals(Format::TIFF, $ext->format()); } public function testFormatJpeg2000(): void { $ext = FileExtension::JP2; $this->assertEquals(Format::JP2, $ext->format()); $ext = FileExtension::J2K; $this->assertEquals(Format::JP2, $ext->format()); $ext = FileExtension::J2C; $this->assertEquals(Format::JP2, $ext->format()); $ext = FileExtension::JPG2; $this->assertEquals(Format::JP2, $ext->format()); $ext = FileExtension::JP2K; $this->assertEquals(Format::JP2, $ext->format()); } public function testFormatHeic(): void { $ext = FileExtension::HEIC; $this->assertEquals(Format::HEIC, $ext->format()); $ext = FileExtension::HEIF; $this->assertEquals(Format::HEIC, $ext->format()); } #[DataProvider('mediaTypesDataProvider')] public function testMediatypes(FileExtension $extension, int $mediaTypeCount, MediaType $mediaType): void { $this->assertCount($mediaTypeCount, $extension->mediaTypes()); $this->assertEquals($mediaType, $extension->mediaType()); } public static function mediaTypesDataProvider(): Generator { yield [FileExtension::JPEG, 4, MediaType::IMAGE_JPEG]; yield [FileExtension::WEBP, 2, MediaType::IMAGE_WEBP]; yield [FileExtension::GIF, 1, MediaType::IMAGE_GIF]; yield [FileExtension::PNG, 2, MediaType::IMAGE_PNG]; yield [FileExtension::AVIF, 2, MediaType::IMAGE_AVIF]; yield [FileExtension::BMP, 9, MediaType::IMAGE_BMP]; yield [FileExtension::TIFF, 1, MediaType::IMAGE_TIFF]; yield [FileExtension::TIF, 1, MediaType::IMAGE_TIFF]; yield [FileExtension::JP2, 4, MediaType::IMAGE_JP2]; yield [FileExtension::HEIC, 3, MediaType::IMAGE_HEIC]; } } ================================================ FILE: tests/Unit/FileTest.php ================================================ assertInstanceOf(File::class, $file); $file = new File('foo'); $this->assertInstanceOf(File::class, $file); } public function testConstructorFromString(): void { $file = new File('foo'); $this->assertInstanceOf(File::class, $file); } public function testConstructorFromResource(): void { $file = new File(fopen('php://temp', 'r')); $this->assertInstanceOf(File::class, $file); } public function testFromPath(): void { $file = File::fromPath($this->getTestResourcePath()); $this->assertInstanceOf(File::class, $file); $this->assertTrue($file->size() > 0); } public function testSave(): void { $file = new File('foo'); $filenames = [ __DIR__ . '/01_file_' . strval(hrtime(true)) . '.test', __DIR__ . '/02_file_' . strval(hrtime(true)) . '.test', ]; foreach ($filenames as $name) { $file->save($name); } foreach ($filenames as $name) { $this->assertFileExists($name); $this->assertEquals('foo', file_get_contents($name)); unlink($name); } } public function testToString(): void { $file = new File('foo'); $string = $file->toString(); $this->assertEquals('foo', $string); $this->assertEquals('foo', $string); } public function testToFilePointer(): void { $file = new File('foo'); $fp = $file->toFilePointer(); $this->assertIsResource($fp); } public function testSize(): void { $file = new File(); $this->assertEquals(0, $file->size()); $file = new File('foo'); $this->assertEquals(3, $file->size()); } } ================================================ FILE: tests/Unit/FormatTest.php ================================================ assertEquals(Format::JPEG, Format::create(Format::JPEG)); $this->assertEquals(Format::JPEG, Format::create('jpg')); $this->assertEquals(Format::JPEG, Format::create('jpeg')); $this->assertEquals(Format::JPEG, Format::create('image/jpeg')); $this->assertEquals(Format::GIF, Format::create('image/gif')); $this->assertEquals(Format::JPEG, Format::create('JPG')); $this->assertEquals(Format::JPEG, Format::create('JPEG')); $this->assertEquals(Format::JPEG, Format::create('IMAGE/JPEG')); $this->assertEquals(Format::GIF, Format::create('IMAGE/GIF')); $this->assertEquals(Format::PNG, Format::create(FileExtension::PNG)); $this->assertEquals(Format::WEBP, Format::create(MediaType::IMAGE_WEBP)); } public function testCreateUnknown(): void { $this->expectException(NotSupportedException::class); Format::create('foo'); } public function testTryCreate(): void { $this->assertEquals(Format::JPEG, Format::tryCreate(Format::JPEG)); $this->assertEquals(Format::JPEG, Format::tryCreate('jpg')); $this->assertEquals(Format::JPEG, Format::tryCreate('jpeg')); $this->assertEquals(Format::JPEG, Format::tryCreate('image/jpeg')); $this->assertEquals(Format::GIF, Format::tryCreate('image/gif')); $this->assertEquals(Format::PNG, Format::tryCreate(FileExtension::PNG)); $this->assertEquals(Format::WEBP, Format::tryCreate(MediaType::IMAGE_WEBP)); $this->assertNull(Format::tryCreate('no-format')); } public function testMediaTypesJpeg(): void { $format = Format::JPEG; $mediaTypes = $format->mediaTypes(); $this->assertIsArray($mediaTypes); $this->assertCount(4, $mediaTypes); $this->assertEquals(MediaType::IMAGE_JPEG, $format->mediaType()); } public function testMediaTypesWebp(): void { $format = Format::WEBP; $mediaTypes = $format->mediaTypes(); $this->assertIsArray($mediaTypes); $this->assertCount(2, $mediaTypes); $this->assertEquals(MediaType::IMAGE_WEBP, $format->mediaType()); } public function testMediaTypesFGif(): void { $format = Format::GIF; $mediaTypes = $format->mediaTypes(); $this->assertIsArray($mediaTypes); $this->assertCount(1, $mediaTypes); $this->assertEquals(MediaType::IMAGE_GIF, $format->mediaType()); } public function testMediaTypesPng(): void { $format = Format::PNG; $mediaTypes = $format->mediaTypes(); $this->assertIsArray($mediaTypes); $this->assertCount(2, $mediaTypes); $this->assertEquals(MediaType::IMAGE_PNG, $format->mediaType()); } public function testMediaTypesAvif(): void { $format = Format::AVIF; $mediaTypes = $format->mediaTypes(); $this->assertIsArray($mediaTypes); $this->assertCount(2, $mediaTypes); $this->assertEquals(MediaType::IMAGE_AVIF, $format->mediaType()); } public function testMediaTypesBmp(): void { $format = Format::BMP; $mediaTypes = $format->mediaTypes(); $this->assertIsArray($mediaTypes); $this->assertCount(9, $mediaTypes); $this->assertEquals(MediaType::IMAGE_BMP, $format->mediaType()); } public function testMediaTypesTiff(): void { $format = Format::TIFF; $mediaTypes = $format->mediaTypes(); $this->assertIsArray($mediaTypes); $this->assertCount(1, $mediaTypes); $this->assertEquals(MediaType::IMAGE_TIFF, $format->mediaType()); } public function testMediaTypesJpeg2000(): void { $format = Format::JP2; $mediaTypes = $format->mediaTypes(); $this->assertIsArray($mediaTypes); $this->assertCount(4, $mediaTypes); $this->assertEquals(MediaType::IMAGE_JP2, $format->mediaType()); } public function testMediaTypesHeic(): void { $format = Format::HEIC; $mediaTypes = $format->mediaTypes(); $this->assertIsArray($mediaTypes); $this->assertCount(3, $mediaTypes); $this->assertEquals(MediaType::IMAGE_HEIC, $format->mediaType()); } public function testEncoderJpeg(): void { $format = Format::JPEG; $this->assertInstanceOf(JpegEncoder::class, $format->encoder()); } public function testEncoderAvif(): void { $format = Format::AVIF; $this->assertInstanceOf(AvifEncoder::class, $format->encoder()); } public function testEncoderWebp(): void { $format = Format::WEBP; $this->assertInstanceOf(WebpEncoder::class, $format->encoder()); } public function testEncoderGif(): void { $format = Format::GIF; $this->assertInstanceOf(GifEncoder::class, $format->encoder()); } public function testEncoderPng(): void { $format = Format::PNG; $this->assertInstanceOf(PngEncoder::class, $format->encoder()); } public function testEncoderBitmap(): void { $format = Format::BMP; $this->assertInstanceOf(BmpEncoder::class, $format->encoder()); } public function testEncoderTiff(): void { $format = Format::TIFF; $this->assertInstanceOf(TiffEncoder::class, $format->encoder()); } public function testEncoderJpep2000(): void { $format = Format::JP2; $this->assertInstanceOf(Jpeg2000Encoder::class, $format->encoder()); } public function testEncoderHeic(): void { $format = Format::HEIC; $this->assertInstanceOf(HeicEncoder::class, $format->encoder()); } public function testFileExtensionsJpeg(): void { $format = Format::JPEG; $extensions = $format->fileExtensions(); $this->assertIsArray($extensions); $this->assertCount(2, $extensions); $this->assertEquals(FileExtension::JPG, $format->fileExtension()); } public function testFileExtensionsWebp(): void { $format = Format::WEBP; $extensions = $format->fileExtensions(); $this->assertIsArray($extensions); $this->assertCount(1, $extensions); $this->assertEquals(FileExtension::WEBP, $format->fileExtension()); } public function testFileExtensionsGif(): void { $format = Format::GIF; $extensions = $format->fileExtensions(); $this->assertIsArray($extensions); $this->assertCount(1, $extensions); $this->assertEquals(FileExtension::GIF, $format->fileExtension()); } public function testFileExtensionsPng(): void { $format = Format::PNG; $extensions = $format->fileExtensions(); $this->assertIsArray($extensions); $this->assertCount(1, $extensions); $this->assertEquals(FileExtension::PNG, $format->fileExtension()); } public function testFileExtensionsAvif(): void { $format = Format::AVIF; $extensions = $format->fileExtensions(); $this->assertIsArray($extensions); $this->assertCount(1, $extensions); $this->assertEquals(FileExtension::AVIF, $format->fileExtension()); } public function testFileExtensionsBmp(): void { $format = Format::BMP; $extensions = $format->fileExtensions(); $this->assertIsArray($extensions); $this->assertCount(1, $extensions); $this->assertEquals(FileExtension::BMP, $format->fileExtension()); } public function testFileExtensionsTiff(): void { $format = Format::TIFF; $extensions = $format->fileExtensions(); $this->assertIsArray($extensions); $this->assertCount(2, $extensions); $this->assertEquals(FileExtension::TIF, $format->fileExtension()); } public function testFileExtensionsJp2(): void { $format = Format::JP2; $extensions = $format->fileExtensions(); $this->assertIsArray($extensions); $this->assertCount(9, $extensions); $this->assertEquals(FileExtension::JP2, $format->fileExtension()); } public function testFileExtensionsHeic(): void { $format = Format::HEIC; $extensions = $format->fileExtensions(); $this->assertIsArray($extensions); $this->assertCount(2, $extensions); $this->assertEquals(FileExtension::HEIC, $format->fileExtension()); } } ================================================ FILE: tests/Unit/Geometry/BezierTest.php ================================================ assertInstanceOf(Bezier::class, $bezier); $this->assertEquals(0, $bezier->count()); } public function testCount(): void { $bezier = new Bezier([ new Point(), new Point(), new Point(), new Point() ]); $this->assertEquals(4, $bezier->count()); } public function testArrayAccess(): void { $bezier = new Bezier([ new Point(), new Point(), new Point(), new Point() ]); $this->assertInstanceOf(Point::class, $bezier[0]); $this->assertInstanceOf(Point::class, $bezier[1]); $this->assertInstanceOf(Point::class, $bezier[2]); $this->assertInstanceOf(Point::class, $bezier[3]); } public function testAddPoint(): void { $bezier = new Bezier([ new Point(), new Point() ]); $this->assertEquals(2, $bezier->count()); $result = $bezier->addPoint(new Point()); $this->assertEquals(3, $bezier->count()); $this->assertInstanceOf(Bezier::class, $result); } public function testFirst(): void { $bezier = new Bezier([ new Point(50, 45), new Point(100, -49), new Point(-100, 100), new Point(200, 300), ]); $this->assertEquals(50, $bezier->first()->x()); $this->assertEquals(45, $bezier->first()->y()); } public function testFirstEmpty(): void { $bezier = new Bezier(); $this->assertNull($bezier->first()); } public function testSecond(): void { $bezier = new Bezier([ new Point(50, 45), new Point(100, -49), new Point(-100, 100), new Point(200, 300), ]); $this->assertEquals(100, $bezier->second()->x()); $this->assertEquals(-49, $bezier->second()->y()); } public function testSecondEmpty(): void { $bezier = new Bezier(); $this->assertNull($bezier->second()); } public function testThird(): void { $bezier = new Bezier([ new Point(50, 45), new Point(100, -49), new Point(-100, 100), new Point(200, 300), ]); $this->assertEquals(-100, $bezier->third()->x()); $this->assertEquals(100, $bezier->third()->y()); } public function testThirdEmpty(): void { $bezier = new Bezier(); $this->assertNull($bezier->third()); } public function testLast(): void { $bezier = new Bezier([ new Point(50, 45), new Point(100, -49), new Point(-100, 100), new Point(200, 300), ]); $this->assertEquals(200, $bezier->last()->x()); $this->assertEquals(300, $bezier->last()->y()); } public function testLastEmpty(): void { $bezier = new Bezier(); $this->assertNull($bezier->last()); } public function testOffsetExists(): void { $bezier = new Bezier(); $this->assertFalse($bezier->offsetExists(0)); $this->assertFalse($bezier->offsetExists(1)); $bezier->addPoint(new Point(0, 0)); $this->assertTrue($bezier->offsetExists(0)); $this->assertFalse($bezier->offsetExists(1)); } public function testOffsetSetUnset(): void { $bezier = new Bezier(); $bezier->offsetSet(0, new Point()); $bezier->offsetSet(2, new Point()); $this->assertTrue($bezier->offsetExists(0)); $this->assertFalse($bezier->offsetExists(1)); $this->assertTrue($bezier->offsetExists(2)); $bezier->offsetUnset(2); $this->assertTrue($bezier->offsetExists(0)); $this->assertFalse($bezier->offsetExists(1)); $this->assertFalse($bezier->offsetExists(2)); } public function testGetSetPivotPoint(): void { $bezier = new Bezier(); $this->assertInstanceOf(Point::class, $bezier->pivot()); $this->assertEquals(0, $bezier->pivot()->x()); $this->assertEquals(0, $bezier->pivot()->y()); $result = $bezier->setPivot(new Point(12, 34)); $this->assertInstanceOf(Bezier::class, $result); $this->assertEquals(12, $bezier->pivot()->x()); $this->assertEquals(34, $bezier->pivot()->y()); } public function testToArray(): void { $bezier = new Bezier([ new Point(50, 50), new Point(100, 50), new Point(-50, -100), new Point(50, 100), ]); $this->assertEquals([50, 50, 100, 50, -50, -100, 50, 100], $bezier->toArray()); } } ================================================ FILE: tests/Unit/Geometry/CircleTest.php ================================================ assertInstanceOf(Circle::class, $circle); $this->assertEquals(100, $circle->diameter()); $this->assertInstanceOf(Point::class, $circle->pivot()); } public function testSetGetDiameter(): void { $circle = new Circle(100, new Point(1, 2)); $this->assertEquals(100, $circle->diameter()); $result = $circle->setDiameter(200); $this->assertInstanceOf(Circle::class, $result); $this->assertEquals(200, $result->diameter()); $this->assertEquals(200, $circle->diameter()); } public function testSetGetRadius(): void { $circle = new Circle(100, new Point(1, 2)); $this->assertEquals(50, $circle->radius()); $result = $circle->setRadius(200); $this->assertInstanceOf(Circle::class, $result); $this->assertEquals(400, $result->diameter()); $this->assertEquals(400, $circle->diameter()); $this->assertEquals(200, $result->radius()); $this->assertEquals(200, $circle->radius()); } } ================================================ FILE: tests/Unit/Geometry/EllipseTest.php ================================================ assertInstanceOf(Ellipse::class, $ellipse); $this->assertEquals(10, $ellipse->width()); $this->assertEquals(20, $ellipse->height()); } public function testPosition(): void { $ellipse = new Ellipse(10, 20, new Point(100, 200)); $this->assertInstanceOf(Point::class, $ellipse->position()); $this->assertEquals(100, $ellipse->position()->x()); $this->assertEquals(200, $ellipse->position()->y()); $this->assertInstanceOf(Point::class, $ellipse->pivot()); $this->assertEquals(100, $ellipse->pivot()->x()); $this->assertEquals(200, $ellipse->pivot()->y()); } public function testSetSize(): void { $ellipse = new Ellipse(10, 20, new Point(100, 200)); $this->assertEquals(10, $ellipse->width()); $this->assertEquals(20, $ellipse->height()); $result = $ellipse->setSize(100, 200); $this->assertInstanceOf(Ellipse::class, $result); $this->assertEquals(100, $ellipse->width()); $this->assertEquals(200, $ellipse->height()); } public function testSetWidthHeight(): void { $ellipse = new Ellipse(10, 20, new Point(100, 200)); $this->assertEquals(10, $ellipse->width()); $this->assertEquals(20, $ellipse->height()); $result = $ellipse->setWidth(100); $this->assertInstanceOf(Ellipse::class, $result); $this->assertEquals(100, $ellipse->width()); $this->assertEquals(20, $ellipse->height()); $result = $ellipse->setHeight(200); $this->assertInstanceOf(Ellipse::class, $result); $this->assertEquals(100, $ellipse->width()); $this->assertEquals(200, $ellipse->height()); } } ================================================ FILE: tests/Unit/Geometry/Factories/BezierFactoryTest.php ================================================ background('f00'); $bezier->border('ff0', 10); $bezier->point(300, 260); $bezier->point(150, 335); $bezier->point(300, 410); }); $bezier = $factory(); $this->assertInstanceOf(Bezier::class, $bezier); $this->assertTrue($bezier->hasBackgroundColor()); $this->assertEquals('f00', $bezier->backgroundColor()); $this->assertEquals('ff0', $bezier->borderColor()); $this->assertEquals(10, $bezier->borderSize()); $this->assertEquals(3, $bezier->count()); } } ================================================ FILE: tests/Unit/Geometry/Factories/CircleFactoryTest.php ================================================ background('fff'); $circle->border('ccc', 10); $circle->radius(100); $circle->diameter(1000); }); $circle = $factory(); $this->assertInstanceOf(Ellipse::class, $circle); $this->assertTrue($circle->hasBackgroundColor()); $this->assertEquals('fff', $circle->backgroundColor()); $this->assertEquals('ccc', $circle->borderColor()); $this->assertEquals(10, $circle->borderSize()); $this->assertEquals(1000, $circle->width()); $this->assertEquals(1000, $circle->height()); } } ================================================ FILE: tests/Unit/Geometry/Factories/DrawableTest.php ================================================ assertInstanceOf(BezierFactory::class, Drawable::bezier()); } public function testCircle(): void { $this->assertInstanceOf(CircleFactory::class, Drawable::circle()); } public function testEllipse(): void { $this->assertInstanceOf(EllipseFactory::class, Drawable::ellipse()); } public function testLine(): void { $this->assertInstanceOf(LineFactory::class, Drawable::line()); } public function testPolygon(): void { $this->assertInstanceOf(PolygonFactory::class, Drawable::polygon()); } public function testRectangle(): void { $this->assertInstanceOf(RectangleFactory::class, Drawable::rectangle()); } } ================================================ FILE: tests/Unit/Geometry/Factories/EllipseFactoryTest.php ================================================ background('fff'); $ellipse->border('ccc', 10); $ellipse->width(100); $ellipse->height(200); $ellipse->size(1000, 2000); }); $ellipse = $factory(); $this->assertInstanceOf(Ellipse::class, $ellipse); $this->assertTrue($ellipse->hasBackgroundColor()); $this->assertEquals('fff', $ellipse->backgroundColor()); $this->assertEquals('ccc', $ellipse->borderColor()); $this->assertEquals(10, $ellipse->borderSize()); $this->assertEquals(1000, $ellipse->width()); $this->assertEquals(2000, $ellipse->height()); } } ================================================ FILE: tests/Unit/Geometry/Factories/LineFactoryTest.php ================================================ color('fff'); $line->background('fff'); $line->border('fff', 10); $line->width(10); $line->from(100, 200); $line->to(300, 400); }); $line = $factory(); $this->assertInstanceOf(Line::class, $line); $this->assertTrue($line->hasBackgroundColor()); $this->assertEquals('fff', $line->backgroundColor()); $this->assertEquals(100, $line->start()->x()); $this->assertEquals(200, $line->start()->y()); $this->assertEquals(300, $line->end()->x()); $this->assertEquals(400, $line->end()->y()); $this->assertEquals(10, $line->width()); } } ================================================ FILE: tests/Unit/Geometry/Factories/PolygonFactoryTest.php ================================================ background('fff'); $polygon->border('ccc', 10); $polygon->point(1, 2); $polygon->point(3, 4); $polygon->point(5, 6); }); $polygon = $factory(); $this->assertInstanceOf(Polygon::class, $polygon); $this->assertTrue($polygon->hasBackgroundColor()); $this->assertEquals('fff', $polygon->backgroundColor()); $this->assertEquals('ccc', $polygon->borderColor()); $this->assertEquals(10, $polygon->borderSize()); $this->assertEquals(3, $polygon->count()); } } ================================================ FILE: tests/Unit/Geometry/Factories/RectangleFactoryTest.php ================================================ background('fff'); $rectangle->border('ccc', 10); $rectangle->width(100); $rectangle->height(200); $rectangle->size(1000, 2000); }); $rectangle = $factory(); $this->assertInstanceOf(Rectangle::class, $rectangle); $this->assertTrue($rectangle->hasBackgroundColor()); $this->assertEquals('fff', $rectangle->backgroundColor()); $this->assertEquals('ccc', $rectangle->borderColor()); $this->assertEquals(10, $rectangle->borderSize()); $this->assertEquals(1000, $rectangle->width()); $this->assertEquals(2000, $rectangle->height()); } } ================================================ FILE: tests/Unit/Geometry/LineTest.php ================================================ assertInstanceOf(Line::class, $line); } public function testPosition(): void { $line = new Line(new Point(1, 2), new Point(3, 4), 10); $this->assertEquals(1, $line->position()->x()); $this->assertEquals(2, $line->position()->y()); } public function testSetGetStart(): void { $line = new Line(new Point(1, 2), new Point(3, 4), 10); $this->assertEquals(1, $line->start()->x()); $this->assertEquals(2, $line->start()->y()); $result = $line->setStart(new Point(10, 20)); $this->assertInstanceOf(Line::class, $result); $this->assertEquals(10, $line->start()->x()); $this->assertEquals(20, $line->start()->y()); } public function testSetGetEnd(): void { $line = new Line(new Point(1, 2), new Point(3, 4), 10); $this->assertEquals(3, $line->end()->x()); $this->assertEquals(4, $line->end()->y()); $result = $line->setEnd(new Point(30, 40)); $this->assertInstanceOf(Line::class, $result); $this->assertEquals(30, $line->end()->x()); $this->assertEquals(40, $line->end()->y()); } public function setFrom(): void { $line = new Line(new Point(1, 2), new Point(3, 4), 10); $this->assertEquals(1, $line->start()->x()); $this->assertEquals(2, $line->start()->y()); $result = $line->from(10, 20); $this->assertInstanceOf(Line::class, $result); $this->assertEquals(10, $line->start()->x()); $this->assertEquals(20, $line->start()->y()); } public function setTo(): void { $line = new Line(new Point(1, 2), new Point(3, 4), 10); $this->assertEquals(3, $line->end()->x()); $this->assertEquals(4, $line->end()->y()); $result = $line->to(30, 40); $this->assertInstanceOf(Line::class, $result); $this->assertEquals(30, $line->end()->x()); $this->assertEquals(40, $line->end()->y()); } public function testSetGetWidth(): void { $line = new Line(new Point(1, 2), new Point(3, 4), 10); $this->assertEquals(10, $line->width()); $result = $line->setWidth(20); $this->assertInstanceOf(Line::class, $result); $this->assertEquals(20, $line->width()); } } ================================================ FILE: tests/Unit/Geometry/PixelTest.php ================================================ setBackgroundColor($color); $this->assertEquals($color, $pixel->backgroundColor()); $this->assertInstanceOf(Pixel::class, $result); } } ================================================ FILE: tests/Unit/Geometry/PointTest.php ================================================ assertInstanceOf(Point::class, $point); $this->assertEquals(0, $point->x()); $this->assertEquals(0, $point->y()); } public function testConstructorWithParameters(): void { $point = new Point(40, 50); $this->assertInstanceOf(Point::class, $point); $this->assertEquals(40, $point->x()); $this->assertEquals(50, $point->y()); } public function testIteration(): void { $point = new Point(40, 50); foreach ($point as $value) { $this->assertIsInt($value); } } public function testGetSetX(): void { $point = new Point(0, 0); $point->setX(100); $this->assertEquals(100, $point->x()); $this->assertEquals(0, $point->y()); } public function testGetSetY(): void { $point = new Point(0, 0); $point->setY(100); $this->assertEquals(0, $point->x()); $this->assertEquals(100, $point->y()); } public function testmoveX(): void { $point = new Point(50, 50); $point->moveX(100); $this->assertEquals(150, $point->x()); $this->assertEquals(50, $point->y()); } public function testmoveY(): void { $point = new Point(50, 50); $point->moveY(100); $this->assertEquals(50, $point->x()); $this->assertEquals(150, $point->y()); } public function testSetPosition(): void { $point = new Point(0, 0); $point->setPosition(100, 200); $this->assertEquals(100, $point->x()); $this->assertEquals(200, $point->y()); } public function testRotate(): void { $point = new Point(30, 0); $point->rotate(90, new Point(0, 0)); $this->assertEquals(0, $point->x()); $this->assertEquals(30, $point->y()); $point->rotate(90, new Point(0, 0)); $this->assertEquals(-30, $point->x()); $this->assertEquals(0, $point->y()); $point = new Point(300, 200); $point->rotate(90, new Point(0, 0)); $this->assertEquals(-200, $point->x()); $this->assertEquals(300, $point->y()); $point = new Point(0, 74); $point->rotate(45, new Point(0, 0)); $this->assertEquals(-52, $point->x()); $this->assertEquals(52, $point->y()); } } ================================================ FILE: tests/Unit/Geometry/PolygonTest.php ================================================ assertInstanceOf(Polygon::class, $poly); $this->assertEquals(0, $poly->count()); } public function testCount(): void { $poly = new Polygon([new Point(), new Point()]); $this->assertEquals(2, $poly->count()); } public function testArrayAccess(): void { $poly = new Polygon([new Point(), new Point()]); $this->assertInstanceOf(Point::class, $poly[0]); $this->assertInstanceOf(Point::class, $poly[1]); } public function testAddPoint(): void { $poly = new Polygon([new Point(), new Point()]); $this->assertEquals(2, $poly->count()); $result = $poly->addPoint(new Point()); $this->assertEquals(3, $poly->count()); $this->assertInstanceOf(Polygon::class, $result); } public function testGetCenterPoint(): void { $poly = new Polygon([ new Point(0, 0), new Point(20, 0), new Point(20, -20), new Point(0, -20), ]); $result = $poly->centerPoint(); $this->assertEquals(10, $result->x()); $this->assertEquals(-10, $result->y()); $poly = new Polygon([ new Point(0, 0), new Point(300, 0), new Point(300, -200), new Point(0, -200), ], new Point(0, 0)); $result = $poly->centerPoint(); $this->assertEquals(150, $result->x()); $this->assertEquals(-100, $result->y()); } public function testGetWidth(): void { $poly = new Polygon([ new Point(12, 45), new Point(-23, -49), new Point(3, 566), ]); $this->assertEquals($poly->width(), 35); } public function testGetHeight(): void { $poly = new Polygon([ new Point(12, 45), new Point(-23, -49), new Point(3, 566), ]); $this->assertEquals(615, $poly->height()); $poly = new Polygon([ new Point(250, 207), new Point(473, 207), new Point(473, 250), new Point(250, 250), ], new Point(250, 250)); $this->assertEquals(43, $poly->height()); } public function testFirst(): void { $poly = new Polygon([ new Point(12, 45), new Point(-23, -49), new Point(3, 566), ]); $this->assertEquals(12, $poly->first()->x()); $this->assertEquals(45, $poly->first()->y()); } public function testFirstEmpty(): void { $poly = new Polygon(); $this->assertNull($poly->first()); } public function testLast(): void { $poly = new Polygon([ new Point(12, 45), new Point(-23, -49), new Point(3, 566), ]); $this->assertEquals(3, $poly->last()->x()); $this->assertEquals(566, $poly->last()->y()); } public function testLastEmpty(): void { $poly = new Polygon(); $this->assertNull($poly->last()); } public function testOffsetExists(): void { $poly = new Polygon(); $this->assertFalse($poly->offsetExists(0)); $this->assertFalse($poly->offsetExists(1)); $poly->addPoint(new Point(0, 0)); $this->assertTrue($poly->offsetExists(0)); $this->assertFalse($poly->offsetExists(1)); } public function testOffsetSetUnset(): void { $poly = new Polygon(); $poly->offsetSet(0, new Point()); $poly->offsetSet(2, new Point()); $this->assertTrue($poly->offsetExists(0)); $this->assertFalse($poly->offsetExists(1)); $this->assertTrue($poly->offsetExists(2)); $poly->offsetUnset(2); $this->assertTrue($poly->offsetExists(0)); $this->assertFalse($poly->offsetExists(1)); $this->assertFalse($poly->offsetExists(2)); } public function testGetSetPivotPoint(): void { $poly = new Polygon(); $this->assertInstanceOf(Point::class, $poly->pivot()); $this->assertEquals(0, $poly->pivot()->x()); $this->assertEquals(0, $poly->pivot()->y()); $result = $poly->setPivot(new Point(12, 34)); $this->assertInstanceOf(Polygon::class, $result); $this->assertEquals(12, $poly->pivot()->x()); $this->assertEquals(34, $poly->pivot()->y()); } public function testGetMostLeftPoint(): void { $poly = new Polygon([ new Point(0, 0), new Point(300, 0), new Point(300, -200), new Point(-32, -200), ], new Point(0, 0)); $result = $poly->mostLeftPoint(); $this->assertEquals(-32, $result->x()); $this->assertEquals(-200, $result->y()); } public function testGetMostRightPoint(): void { $poly = new Polygon([ new Point(0, 0), new Point(350, 0), new Point(300, -200), new Point(-32, -200), ], new Point(0, 0)); $result = $poly->mostRightPoint(); $this->assertEquals(350, $result->x()); $this->assertEquals(0, $result->y()); } public function testGetMostTopPoint(): void { $poly = new Polygon([ new Point(0, 100), new Point(350, 0), new Point(300, -200), new Point(-32, 200), ], new Point(0, 0)); $result = $poly->mostTopPoint(); $this->assertEquals(-32, $result->x()); $this->assertEquals(200, $result->y()); } public function testGetMostBottomPoint(): void { $poly = new Polygon([ new Point(0, 100), new Point(350, 0), new Point(300, -200), new Point(-32, 200), ], new Point(0, 0)); $result = $poly->mostBottomPoint(); $this->assertEquals(300, $result->x()); $this->assertEquals(-200, $result->y()); } public function testAlignCenter(): void { $poly = new Polygon([ new Point(0, 0), new Point(300, 0), new Point(300, -200), new Point(0, -200), ], new Point(0, 0)); $result = $poly->align('center'); $this->assertInstanceOf(Polygon::class, $result); $this->assertEquals(-150, $result[0]->x()); $this->assertEquals(0, $result[0]->y()); $this->assertEquals(150, $result[1]->x()); $this->assertEquals(0, $result[1]->y()); $this->assertEquals(150, $result[2]->x()); $this->assertEquals(-200, $result[2]->y()); $this->assertEquals(-150, $result[3]->x()); $this->assertEquals(-200, $result[3]->y()); $poly = new Polygon([ new Point(0, 0), new Point(300, 0), new Point(300, -200), new Point(0, -200), ], new Point(-1000, -1000)); $result = $poly->align('center'); $this->assertInstanceOf(Polygon::class, $result); $this->assertEquals(-1150, $result[0]->x()); $this->assertEquals(0, $result[0]->y()); $this->assertEquals(-850, $result[1]->x()); $this->assertEquals(0, $result[1]->y()); $this->assertEquals(-850, $result[2]->x()); $this->assertEquals(-200, $result[2]->y()); $this->assertEquals(-1150, $result[3]->x()); $this->assertEquals(-200, $result[3]->y()); } public function testAlignLeft(): void { $poly = new Polygon([ new Point(0, 0), new Point(300, 0), new Point(300, -200), new Point(0, -200), ], new Point(100, 100)); $result = $poly->align('left'); $this->assertInstanceOf(Polygon::class, $result); $this->assertEquals(100, $result[0]->x()); $this->assertEquals(0, $result[0]->y()); $this->assertEquals(400, $result[1]->x()); $this->assertEquals(0, $result[1]->y()); $this->assertEquals(400, $result[2]->x()); $this->assertEquals(-200, $result[2]->y()); $this->assertEquals(100, $result[3]->x()); $this->assertEquals(-200, $result[3]->y()); $poly = new Polygon([ new Point(0, 0), new Point(300, 0), new Point(300, -200), new Point(0, -200), ], new Point(-1000, -1000)); $result = $poly->align('left'); $this->assertInstanceOf(Polygon::class, $result); $this->assertEquals(-1000, $result[0]->x()); $this->assertEquals(0, $result[0]->y()); $this->assertEquals(-700, $result[1]->x()); $this->assertEquals(0, $result[1]->y()); $this->assertEquals(-700, $result[2]->x()); $this->assertEquals(-200, $result[2]->y()); $this->assertEquals(-1000, $result[3]->x()); $this->assertEquals(-200, $result[3]->y()); } public function testAlignRight(): void { $poly = new Polygon([ new Point(0, 0), new Point(300, 0), new Point(300, -200), new Point(0, -200), ], new Point(100, 100)); $result = $poly->align('right'); $this->assertInstanceOf(Polygon::class, $result); $this->assertEquals(-200, $result[0]->x()); $this->assertEquals(0, $result[0]->y()); $this->assertEquals(100, $result[1]->x()); $this->assertEquals(0, $result[1]->y()); $this->assertEquals(100, $result[2]->x()); $this->assertEquals(-200, $result[2]->y()); $this->assertEquals(-200, $result[3]->x()); $this->assertEquals(-200, $result[3]->y()); $poly = new Polygon([ new Point(0, 0), new Point(300, 0), new Point(300, -200), new Point(0, -200), ], new Point(-1000, -1000)); $result = $poly->align('right'); $this->assertInstanceOf(Polygon::class, $result); $this->assertEquals(-1300, $result[0]->x()); $this->assertEquals(0, $result[0]->y()); $this->assertEquals(-1000, $result[1]->x()); $this->assertEquals(0, $result[1]->y()); $this->assertEquals(-1000, $result[2]->x()); $this->assertEquals(-200, $result[2]->y()); $this->assertEquals(-1300, $result[3]->x()); $this->assertEquals(-200, $result[3]->y()); } public function testValignMiddle(): void { $poly = new Polygon([ new Point(-21, -22), new Point(91, -135), new Point(113, -113), new Point(0, 0), ], new Point(250, 250)); $result = $poly->valign('middle'); $this->assertInstanceOf(Polygon::class, $result); $this->assertEquals(-21, $result[0]->x()); $this->assertEquals(296, $result[0]->y()); $this->assertEquals(91, $result[1]->x()); $this->assertEquals(183, $result[1]->y()); $this->assertEquals(113, $result[2]->x()); $this->assertEquals(205, $result[2]->y()); $this->assertEquals(0, $result[3]->x()); $this->assertEquals(318, $result[3]->y()); } public function testValignTop(): void { $poly = new Polygon([ new Point(-21, -22), new Point(91, -135), new Point(113, -113), new Point(0, 0), ], new Point(250, 250)); $result = $poly->valign('top'); $this->assertInstanceOf(Polygon::class, $result); $this->assertEquals(-21, $result[0]->x()); $this->assertEquals(363, $result[0]->y()); $this->assertEquals(91, $result[1]->x()); $this->assertEquals(250, $result[1]->y()); $this->assertEquals(113, $result[2]->x()); $this->assertEquals(272, $result[2]->y()); $this->assertEquals(0, $result[3]->x()); $this->assertEquals(385, $result[3]->y()); } public function testMovePoints(): void { $poly = new Polygon([ new Point(10, 20), new Point(30, 40) ]); $result = $poly->movePointsX(100); $this->assertEquals(110, $result[0]->x()); $this->assertEquals(20, $result[0]->y()); $this->assertEquals(130, $result[1]->x()); $this->assertEquals(40, $result[1]->y()); $result = $poly->movePointsY(200); $this->assertEquals(110, $result[0]->x()); $this->assertEquals(220, $result[0]->y()); $this->assertEquals(130, $result[1]->x()); $this->assertEquals(240, $result[1]->y()); } public function testRotate(): void { $poly = new Polygon([ new Point(0, 0), new Point(50, 0), new Point(50, -50), new Point(0, -50), ]); $result = $poly->rotate(45); $this->assertInstanceOf(Polygon::class, $result); $this->assertEquals(0, $result[0]->x()); $this->assertEquals(0, $result[0]->y()); $this->assertEquals(35, $result[1]->x()); $this->assertEquals(35, $result[1]->y()); $this->assertEquals(70, $result[2]->x()); $this->assertEquals(0, $result[2]->y()); $this->assertEquals(35, $result[3]->x()); $this->assertEquals(-35, $result[3]->y()); } public function testToArray(): void { $poly = new Polygon([ new Point(0, 0), new Point(50, 0), new Point(50, -50), new Point(0, -50), ]); $this->assertEquals([0, 0, 50, 0, 50, -50, 0, -50], $poly->toArray()); } } ================================================ FILE: tests/Unit/Geometry/RectangleResizerTest.php ================================================ assertInstanceOf(RectangleResizer::class, $resizer); $resizer = RectangleResizer::to(height: 100); $this->assertInstanceOf(RectangleResizer::class, $resizer); $resizer = RectangleResizer::to(100); $this->assertInstanceOf(RectangleResizer::class, $resizer); $resizer = RectangleResizer::to(100, 100); $this->assertInstanceOf(RectangleResizer::class, $resizer); } public function testToWidth(): void { $resizer = new RectangleResizer(); $result = $resizer->toWidth(100); $this->assertInstanceOf(RectangleResizer::class, $result); } public function testToHeight(): void { $resizer = new RectangleResizer(); $result = $resizer->toHeight(100); $this->assertInstanceOf(RectangleResizer::class, $result); } public function testToSize(): void { $resizer = new RectangleResizer(); $resizer = $resizer->toSize(new Rectangle(200, 100)); $this->assertInstanceOf(RectangleResizer::class, $resizer); } /** * @param $resizeParameters array */ #[DataProvider('resizeDataProvider')] public function testResize(Rectangle $input, array $resizeParameters, Rectangle $result): void { $resizer = new RectangleResizer(...$resizeParameters); $resized = $resizer->resize($input); $this->assertEquals($result->width(), $resized->width()); $this->assertEquals($result->height(), $resized->height()); } public static function resizeDataProvider(): Generator { yield [new Rectangle(300, 200), ['width' => 150], new Rectangle(150, 200)]; yield [new Rectangle(300, 200), ['height' => 150], new Rectangle(300, 150)]; yield [new Rectangle(300, 200), ['width' => 20, 'height' => 10], new Rectangle(20, 10)]; yield [new Rectangle(300, 200), [], new Rectangle(300, 200)]; } /** * @param $resizeParameters array */ #[DataProvider('resizeDownDataProvider')] public function testResizeDown(Rectangle $input, array $resizeParameters, Rectangle $result): void { $resizer = new RectangleResizer(...$resizeParameters); $resized = $resizer->resizeDown($input); $this->assertEquals($result->width(), $resized->width()); $this->assertEquals($result->height(), $resized->height()); } public static function resizeDownDataProvider(): Generator { yield [new Rectangle(800, 600), ['width' => 1000, 'height' => 2000], new Rectangle(800, 600)]; yield [new Rectangle(800, 600), ['width' => 400, 'height' => 1000], new Rectangle(400, 600)]; yield [new Rectangle(800, 600), ['width' => 1000, 'height' => 400], new Rectangle(800, 400)]; yield [new Rectangle(800, 600), ['width' => 400, 'height' => 300], new Rectangle(400, 300)]; yield [new Rectangle(800, 600), ['width' => 1000], new Rectangle(800, 600)]; yield [new Rectangle(800, 600), ['height' => 1000], new Rectangle(800, 600)]; yield [new Rectangle(800, 600), [], new Rectangle(800, 600)]; } /** * @param $resizeParameters array */ #[DataProvider('scaleDataProvider')] public function testScale(Rectangle $input, array $resizeParameters, Rectangle $result): void { $resizer = new RectangleResizer(...$resizeParameters); $resized = $resizer->scale($input); $this->assertEquals($result->width(), $resized->width()); $this->assertEquals($result->height(), $resized->height()); } public static function scaleDataProvider(): Generator { yield [new Rectangle(800, 600), ['width' => 1000, 'height' => 2000], new Rectangle(1000, 750)]; yield [new Rectangle(800, 600), ['width' => 2000, 'height' => 1000], new Rectangle(1333, 1000)]; yield [new Rectangle(800, 600), ['height' => 3000], new Rectangle(4000, 3000)]; yield [new Rectangle(800, 600), ['width' => 8000], new Rectangle(8000, 6000)]; yield [new Rectangle(800, 600), ['width' => 100, 'height' => 400], new Rectangle(100, 75)]; yield [new Rectangle(800, 600), ['width' => 400, 'height' => 100], new Rectangle(133, 100)]; yield [new Rectangle(800, 600), ['height' => 300], new Rectangle(400, 300)]; yield [new Rectangle(800, 600), ['width' => 80], new Rectangle(80, 60)]; yield [new Rectangle(640, 480), ['width' => 225], new Rectangle(225, 169)]; yield [new Rectangle(640, 480), ['width' => 223], new Rectangle(223, 167)]; yield [new Rectangle(600, 800), ['width' => 300, 'height' => 300], new Rectangle(225, 300)]; yield [new Rectangle(800, 600), ['width' => 400, 'height' => 10], new Rectangle(13, 10)]; yield [new Rectangle(800, 600), ['width' => 1000, 'height' => 1200], new Rectangle(1000, 750)]; yield [new Rectangle(12000, 12), ['width' => 4000, 'height' => 3000], new Rectangle(4000, 4)]; yield [new Rectangle(12, 12000), ['width' => 4000, 'height' => 3000], new Rectangle(3, 3000)]; yield [new Rectangle(12000, 6000), ['width' => 4000, 'height' => 3000], new Rectangle(4000, 2000)]; yield [new Rectangle(3, 3000), ['height' => 300], new Rectangle(1, 300)]; yield [new Rectangle(800, 600), [], new Rectangle(800, 600)]; } /** * @param $resizeParameters array */ #[DataProvider('scaleDownDataProvider')] public function testScaleDown(Rectangle $input, array $resizeParameters, Rectangle $result): void { $resizer = new RectangleResizer(...$resizeParameters); $resized = $resizer->scaleDown($input); $this->assertEquals($result->width(), $resized->width()); $this->assertEquals($result->height(), $resized->height()); } public static function scaleDownDataProvider(): Generator { yield [new Rectangle(800, 600), ['width' => 1000, 'height' => 2000], new Rectangle(800, 600)]; yield [new Rectangle(800, 600), ['width' => 1000, 'height' => 600], new Rectangle(800, 600)]; yield [new Rectangle(800, 600), ['width' => 1000, 'height' => 300], new Rectangle(400, 300)]; yield [new Rectangle(800, 600), ['width' => 400, 'height' => 1000], new Rectangle(400, 300)]; yield [new Rectangle(800, 600), ['width' => 400], new Rectangle(400, 300)]; yield [new Rectangle(800, 600), ['height' => 300], new Rectangle(400, 300)]; yield [new Rectangle(800, 600), ['width' => 1000], new Rectangle(800, 600)]; yield [new Rectangle(800, 600), ['height' => 1000], new Rectangle(800, 600)]; yield [new Rectangle(800, 600), ['width' => 100], new Rectangle(100, 75)]; yield [new Rectangle(800, 600), ['width' => 300, 'height' => 200], new Rectangle(267, 200)]; yield [new Rectangle(600, 800), ['width' => 300, 'height' => 300], new Rectangle(225, 300)]; yield [new Rectangle(800, 600), ['width' => 400, 'height' => 10], new Rectangle(13, 10)]; yield [new Rectangle(3, 3000), ['height' => 300], new Rectangle(1, 300)]; yield [new Rectangle(800, 600), [], new Rectangle(800, 600)]; } #[DataProvider('coverDataProvider')] public function testCover(Rectangle $origin, Rectangle $target, Rectangle $result): void { $resizer = new RectangleResizer(); $resizer->toSize($target); $resized = $resizer->cover($origin); $this->assertEquals($result->width(), $resized->width()); $this->assertEquals($result->height(), $resized->height()); } public static function coverDataProvider(): Generator { yield [new Rectangle(800, 600), new Rectangle(100, 100), new Rectangle(133, 100)]; yield [new Rectangle(800, 600), new Rectangle(200, 100), new Rectangle(200, 150)]; yield [new Rectangle(800, 600), new Rectangle(100, 200), new Rectangle(267, 200)]; yield [new Rectangle(800, 600), new Rectangle(2000, 10), new Rectangle(2000, 1500)]; yield [new Rectangle(800, 600), new Rectangle(10, 2000), new Rectangle(2667, 2000)]; yield [new Rectangle(800, 600), new Rectangle(800, 600), new Rectangle(800, 600)]; yield [new Rectangle(400, 300), new Rectangle(120, 120), new Rectangle(160, 120)]; yield [new Rectangle(600, 800), new Rectangle(100, 100), new Rectangle(100, 133)]; yield [new Rectangle(100, 100), new Rectangle(800, 600), new Rectangle(800, 800)]; } #[DataProvider('containDataProvider')] public function testContain(Rectangle $origin, Rectangle $target, Rectangle $result): void { $resizer = new RectangleResizer(); $resizer->toSize($target); $resized = $resizer->contain($origin); $this->assertEquals($result->width(), $resized->width()); $this->assertEquals($result->height(), $resized->height()); } public static function containDataProvider(): Generator { yield [new Rectangle(800, 600), new Rectangle(100, 100), new Rectangle(100, 75)]; yield [new Rectangle(800, 600), new Rectangle(200, 100), new Rectangle(133, 100)]; yield [new Rectangle(800, 600), new Rectangle(100, 200), new Rectangle(100, 75)]; yield [new Rectangle(800, 600), new Rectangle(2000, 10), new Rectangle(13, 10)]; yield [new Rectangle(800, 600), new Rectangle(10, 2000), new Rectangle(10, 8)]; yield [new Rectangle(800, 600), new Rectangle(800, 600), new Rectangle(800, 600)]; yield [new Rectangle(400, 300), new Rectangle(120, 120), new Rectangle(120, 90)]; yield [new Rectangle(600, 800), new Rectangle(100, 100), new Rectangle(75, 100)]; yield [new Rectangle(100, 100), new Rectangle(800, 600), new Rectangle(600, 600)]; } #[DataProvider('cropDataProvider')] public function testCrop(Rectangle $origin, Rectangle $target, string $position, Rectangle $result): void { $resizer = new RectangleResizer(); $resizer->toSize($target); $resized = $resizer->crop($origin, $position); $this->assertEquals($result->width(), $resized->width()); $this->assertEquals($result->height(), $resized->height()); $this->assertEquals($result->pivot()->x(), $resized->pivot()->x()); $this->assertEquals($result->pivot()->y(), $resized->pivot()->y()); } public static function cropDataProvider(): Generator { yield [ new Rectangle(800, 600), new Rectangle(100, 100), 'center', new Rectangle(100, 100, new Point(350, 250)) ]; yield [ new Rectangle(800, 600), new Rectangle(200, 100), 'center', new Rectangle(200, 100, new Point(300, 250)) ]; yield [ new Rectangle(800, 600), new Rectangle(100, 200), 'center', new Rectangle(100, 200, new Point(350, 200)) ]; yield [ new Rectangle(800, 600), new Rectangle(2000, 10), 'center', new Rectangle(2000, 10, new Point(-600, 295)) ]; yield [ new Rectangle(800, 600), new Rectangle(10, 2000), 'center', new Rectangle(10, 2000, new Point(395, -700)) ]; yield [ new Rectangle(800, 600), new Rectangle(800, 600), 'center', new Rectangle(800, 600, new Point(0, 0)) ]; yield [ new Rectangle(400, 300), new Rectangle(120, 120), 'center', new Rectangle(120, 120, new Point(140, 90)) ]; yield [ new Rectangle(600, 800), new Rectangle(100, 100), 'center', new Rectangle(100, 100, new Point(250, 350)) ]; } } ================================================ FILE: tests/Unit/Geometry/RectangleTest.php ================================================ assertEquals(0, $rectangle[0]->x()); $this->assertEquals(0, $rectangle[0]->y()); $this->assertEquals(300, $rectangle[1]->x()); $this->assertEquals(0, $rectangle[1]->y()); $this->assertEquals(300, $rectangle[2]->x()); $this->assertEquals(-200, $rectangle[2]->y()); $this->assertEquals(0, $rectangle[3]->x()); $this->assertEquals(-200, $rectangle[3]->y()); $this->assertEquals(300, $rectangle->width()); $this->assertEquals(200, $rectangle->height()); } public function testSetSize(): void { $rectangle = new Rectangle(300, 200); $rectangle->setSize(12, 34); $this->assertEquals(12, $rectangle->width()); $this->assertEquals(34, $rectangle->height()); } public function testSetWidth(): void { $rectangle = new Rectangle(300, 200); $this->assertEquals(300, $rectangle->width()); $rectangle->setWidth(400); $this->assertEquals(400, $rectangle->width()); } public function testSetHeight(): void { $rectangle = new Rectangle(300, 200); $this->assertEquals(200, $rectangle->height()); $rectangle->setHeight(800); $this->assertEquals(800, $rectangle->height()); } public function testGetAspectRatio(): void { $size = new Rectangle(800, 600); $this->assertEquals(1.333, round($size->aspectRatio(), 3)); $size = new Rectangle(100, 100); $this->assertEquals(1, $size->aspectRatio()); $size = new Rectangle(1920, 1080); $this->assertEquals(1.778, round($size->aspectRatio(), 3)); } public function testFitsInto(): void { $box = new Rectangle(800, 600); $fits = $box->fitsInto(new Rectangle(100, 100)); $this->assertFalse($fits); $box = new Rectangle(800, 600); $fits = $box->fitsInto(new Rectangle(1000, 100)); $this->assertFalse($fits); $box = new Rectangle(800, 600); $fits = $box->fitsInto(new Rectangle(100, 1000)); $this->assertFalse($fits); $box = new Rectangle(800, 600); $fits = $box->fitsInto(new Rectangle(800, 600)); $this->assertTrue($fits); $box = new Rectangle(800, 600); $fits = $box->fitsInto(new Rectangle(1000, 1000)); $this->assertTrue($fits); $box = new Rectangle(100, 100); $fits = $box->fitsInto(new Rectangle(800, 600)); $this->assertTrue($fits); $box = new Rectangle(100, 100); $fits = $box->fitsInto(new Rectangle(80, 60)); $this->assertFalse($fits); } public function testIsLandscape(): void { $box = new Rectangle(100, 100); $this->assertFalse($box->isLandscape()); $box = new Rectangle(100, 200); $this->assertFalse($box->isLandscape()); $box = new Rectangle(300, 200); $this->assertTrue($box->isLandscape()); } public function testIsPortrait(): void { $box = new Rectangle(100, 100); $this->assertFalse($box->isPortrait()); $box = new Rectangle(200, 100); $this->assertFalse($box->isPortrait()); $box = new Rectangle(200, 300); $this->assertTrue($box->isPortrait()); } public function testSetGetPivot(): void { $box = new Rectangle(800, 600); $pivot = $box->pivot(); $this->assertInstanceOf(Point::class, $pivot); $this->assertEquals(0, $pivot->x()); $result = $box->setPivot(new Point(10, 0)); $this->assertInstanceOf(Rectangle::class, $result); $this->assertEquals(10, $box->pivot()->x()); } public function testAlignPivot(): void { $box = new Rectangle(640, 480); $this->assertEquals(0, $box->pivot()->x()); $this->assertEquals(0, $box->pivot()->y()); $box->movePivot('top-left', 3, 3); $this->assertEquals(3, $box->pivot()->x()); $this->assertEquals(3, $box->pivot()->y()); $box->movePivot('top', 3, 3); $this->assertEquals(323, $box->pivot()->x()); $this->assertEquals(3, $box->pivot()->y()); $box->movePivot('top-right', 3, 3); $this->assertEquals(637, $box->pivot()->x()); $this->assertEquals(3, $box->pivot()->y()); $box->movePivot('left', 3, 3); $this->assertEquals(3, $box->pivot()->x()); $this->assertEquals(243, $box->pivot()->y()); $box->movePivot('center', 3, 3); $this->assertEquals(323, $box->pivot()->x()); $this->assertEquals(243, $box->pivot()->y()); $box->movePivot('right', 3, 3); $this->assertEquals(637, $box->pivot()->x()); $this->assertEquals(243, $box->pivot()->y()); $box->movePivot('bottom-left', 3, 3); $this->assertEquals(3, $box->pivot()->x()); $this->assertEquals(477, $box->pivot()->y()); $box->movePivot('bottom', 3, 3); $this->assertEquals(323, $box->pivot()->x()); $this->assertEquals(477, $box->pivot()->y()); $result = $box->movePivot('bottom-right', 3, 3); $this->assertEquals(637, $box->pivot()->x()); $this->assertEquals(477, $box->pivot()->y()); $this->assertInstanceOf(Rectangle::class, $result); } public function testAlignPivotTo(): void { $container = new Rectangle(800, 600); $size = new Rectangle(200, 100); $size->alignPivotTo($container, 'center'); $this->assertEquals(300, $size->pivot()->x()); $this->assertEquals(250, $size->pivot()->y()); $container = new Rectangle(800, 600); $size = new Rectangle(100, 100); $size->alignPivotTo($container, 'center'); $this->assertEquals(350, $size->pivot()->x()); $this->assertEquals(250, $size->pivot()->y()); $container = new Rectangle(800, 600); $size = new Rectangle(800, 600); $size->alignPivotTo($container, 'center'); $this->assertEquals(0, $size->pivot()->x()); $this->assertEquals(0, $size->pivot()->y()); $container = new Rectangle(100, 100); $size = new Rectangle(800, 600); $size->alignPivotTo($container, 'center'); $this->assertEquals(-350, $size->pivot()->x()); $this->assertEquals(-250, $size->pivot()->y()); $container = new Rectangle(100, 100); $size = new Rectangle(800, 600); $size->alignPivotTo($container, 'bottom-right'); $this->assertEquals(-700, $size->pivot()->x()); $this->assertEquals(-500, $size->pivot()->y()); } public function testgetRelativePositionTo(): void { $container = new Rectangle(800, 600); $input = new Rectangle(200, 100); $container->movePivot('top-left'); $input->movePivot('top-left'); $pos = $container->relativePositionTo($input); $this->assertEquals(0, $pos->x()); $this->assertEquals(0, $pos->y()); $container = new Rectangle(800, 600); $input = new Rectangle(200, 100); $container->movePivot('center'); $input->movePivot('top-left'); $pos = $container->relativePositionTo($input); $this->assertEquals(400, $pos->x()); $this->assertEquals(300, $pos->y()); $container = new Rectangle(800, 600); $input = new Rectangle(200, 100); $container->movePivot('bottom-right'); $input->movePivot('top-right'); $pos = $container->relativePositionTo($input); $this->assertEquals(600, $pos->x()); $this->assertEquals(600, $pos->y()); $container = new Rectangle(800, 600); $input = new Rectangle(200, 100); $container->movePivot('center'); $input->movePivot('center'); $pos = $container->relativePositionTo($input); $this->assertEquals(300, $pos->x()); $this->assertEquals(250, $pos->y()); $container = new Rectangle(100, 200); $input = new Rectangle(100, 100); $container->movePivot('center'); $input->movePivot('center'); $pos = $container->relativePositionTo($input); $this->assertEquals(0, $pos->x()); $this->assertEquals(50, $pos->y()); } public function testTopLeftPoint(): void { $rectangle = new Rectangle(800, 600); $this->assertInstanceOf(PointInterface::class, $rectangle->topLeftPoint()); } public function testBottomRightPoint(): void { $rectangle = new Rectangle(800, 600); $this->assertInstanceOf(PointInterface::class, $rectangle->bottomRightPoint()); } public function testResize(): void { $rectangle = new Rectangle(800, 600); $result = $rectangle->resize(300, 200); $this->assertInstanceOf(Rectangle::class, $result); $this->assertEquals(300, $result->width()); $this->assertEquals(200, $result->height()); } public function testResizeDown(): void { $rectangle = new Rectangle(800, 600); $result = $rectangle->resizeDown(3000, 200); $this->assertInstanceOf(Rectangle::class, $result); $this->assertEquals(800, $result->width()); $this->assertEquals(200, $result->height()); } public function testScale(): void { $rectangle = new Rectangle(800, 600); $result = $rectangle->scale(height: 1200); $this->assertInstanceOf(Rectangle::class, $result); $this->assertEquals(800 * 2, $result->width()); $this->assertEquals(600 * 2, $result->height()); } public function testScaleDown(): void { $rectangle = new Rectangle(800, 600); $result = $rectangle->scaleDown(height: 1200); $this->assertInstanceOf(Rectangle::class, $result); $this->assertEquals(800, $result->width()); $this->assertEquals(600, $result->height()); } public function testCover(): void { $rectangle = new Rectangle(800, 600); $result = $rectangle->cover(400, 100); $this->assertInstanceOf(Rectangle::class, $result); $this->assertEquals(400, $result->width()); $this->assertEquals(300, $result->height()); } public function testContain(): void { $rectangle = new Rectangle(800, 600); $result = $rectangle->contain(1600, 1200); $this->assertInstanceOf(Rectangle::class, $result); $this->assertEquals(1600, $result->width()); $this->assertEquals(1200, $result->height()); } public function testContainMax(): void { $rectangle = new Rectangle(800, 600); $result = $rectangle->containMax(1600, 1200); $this->assertInstanceOf(Rectangle::class, $result); $this->assertEquals(800, $result->width()); $this->assertEquals(600, $result->height()); } public function testDebugInfo(): void { $info = (new Rectangle(800, 600))->__debugInfo(); $this->assertEquals(800, $info['width']); $this->assertEquals(600, $info['height']); } } ================================================ FILE: tests/Unit/Geometry/Traits/HasBackgroundColorTest.php ================================================ getTestObject(); $this->assertNull($object->backgroundColor()); $this->assertFalse($object->hasBackgroundColor()); $object->setBackgroundColor('fff'); $this->assertEquals('fff', $object->backgroundColor()); $this->assertTrue($object->hasBackgroundColor()); } } ================================================ FILE: tests/Unit/Geometry/Traits/HasBorderTest.php ================================================ getTestObject(); $this->assertNull($object->borderColor()); $this->assertEquals(0, $object->borderSize()); $this->assertFalse($object->hasBorder()); $object->setBorder('fff', 10); $this->assertEquals('fff', $object->borderColor()); $this->assertEquals(10, $object->borderSize()); $this->assertTrue($object->hasBorder()); } public function testSetBorderSize(): void { $object = $this->getTestObject(); $this->assertEquals(0, $object->borderSize()); $object->setBorderSize(10); $this->assertEquals(10, $object->borderSize()); } public function testSetBorderColor(): void { $object = $this->getTestObject(); $this->assertNull($object->borderColor()); $object->setBorderColor('fff'); $this->assertEquals('fff', $object->borderColor()); $this->assertFalse($object->hasBorder()); } public function testHasBorder(): void { $object = $this->getTestObject(); $this->assertFalse($object->hasBorder()); $object->setBorderColor('fff'); $this->assertFalse($object->hasBorder()); $object->setBorderSize(1); $this->assertTrue($object->hasBorder()); } } ================================================ FILE: tests/Unit/ImageManagerTestGd.php ================================================ assertInstanceOf(ImageManager::class, $manager); $manager = new ImageManager(Driver::class); $this->assertInstanceOf(ImageManager::class, $manager); } public function testWithDriver(): void { $manager = ImageManager::withDriver(new Driver()); $this->assertInstanceOf(ImageManager::class, $manager); $manager = ImageManager::withDriver(Driver::class); $this->assertInstanceOf(ImageManager::class, $manager); } public function testDriver(): void { $driver = new Driver(); $manager = ImageManager::withDriver($driver); $this->assertEquals($driver, $manager->driver()); } public function testDriverStatic(): void { $manager = ImageManager::gd(); $this->assertInstanceOf(ImageManager::class, $manager); } public function testCreate(): void { $manager = new ImageManager(Driver::class); $image = $manager->create(5, 4); $this->assertInstanceOf(ImageInterface::class, $image); } public function testAnimate(): void { $manager = new ImageManager(Driver::class); $image = $manager->animate(function ($animation): void { $animation->add($this->getTestResourcePath('red.gif'), .25); }); $this->assertInstanceOf(ImageInterface::class, $image); } public function testRead(): void { $manager = new ImageManager(Driver::class); $image = $manager->read($this->getTestResourcePath('red.gif')); $this->assertInstanceOf(ImageInterface::class, $image); } public function testReadWithDecoderClassname(): void { $manager = new ImageManager(Driver::class); $image = $manager->read($this->getTestResourcePath('red.gif'), FilePathImageDecoder::class); $this->assertInstanceOf(ImageInterface::class, $image); } public function testReadWithDecoderInstance(): void { $manager = new ImageManager(Driver::class); $image = $manager->read($this->getTestResourcePath('red.gif'), new FilePathImageDecoder()); $this->assertInstanceOf(ImageInterface::class, $image); } public function testReadWithDecoderClassnameArray(): void { $manager = new ImageManager(Driver::class); $image = $manager->read($this->getTestResourcePath('red.gif'), [FilePathImageDecoder::class]); $this->assertInstanceOf(ImageInterface::class, $image); } public function testReadWithDecoderInstanceArray(): void { $manager = new ImageManager(Driver::class); $image = $manager->read($this->getTestResourcePath('red.gif'), [new FilePathImageDecoder()]); $this->assertInstanceOf(ImageInterface::class, $image); } public function testReadWithDecoderInstanceArrayMultiple(): void { $manager = new ImageManager(Driver::class); $image = $manager->read($this->getTestResourcePath('red.gif'), [ new BinaryImageDecoder(), new FilePathImageDecoder(), ]); $this->assertInstanceOf(ImageInterface::class, $image); } public function testReadWithRotationAdjustment(): void { $manager = new ImageManager(Driver::class); $image = $manager->read($this->getTestResourcePath('orientation.jpg')); $this->assertColor(1, 0, 254, 255, $image->pickColor(3, 3)); } public function testReadWithoutRotationAdjustment(): void { $manager = new ImageManager(Driver::class, autoOrientation: false); $image = $manager->read($this->getTestResourcePath('orientation.jpg')); $this->assertColor(250, 2, 3, 255, $image->pickColor(3, 3)); } public function testReadAnimation(): void { $manager = new ImageManager(Driver::class); $image = $manager->read($this->getTestResourcePath('animation.gif')); $this->assertTrue($image->isAnimated()); } public function testReadAnimationDiscarded(): void { $manager = new ImageManager(Driver::class, decodeAnimation: false); $image = $manager->read($this->getTestResourcePath('animation.gif')); $this->assertFalse($image->isAnimated()); } public function testApplyBlendingColorDefault(): void { $manager = new ImageManager(Driver::class); $image = $manager->read($this->getTestResourcePath('blocks.png')); $result = $image->blendTransparency(); $this->assertColor(255, 255, 255, 255, $image->pickColor(530, 0)); $this->assertColor(255, 255, 255, 255, $result->pickColor(530, 0)); } public function testApplyBlendingColorConfigured(): void { $manager = new ImageManager(Driver::class, blendingColor: 'ff5500'); $image = $manager->read($this->getTestResourcePath('blocks.png')); $result = $image->blendTransparency(); $this->assertColor(255, 85, 0, 255, $image->pickColor(530, 0)); $this->assertColor(255, 85, 0, 255, $result->pickColor(530, 0)); } } ================================================ FILE: tests/Unit/ImageManagerTestImagick.php ================================================ assertInstanceOf(ImageManager::class, $manager); $manager = new ImageManager(Driver::class); $this->assertInstanceOf(ImageManager::class, $manager); } public function testWithDriver(): void { $manager = ImageManager::withDriver(new Driver()); $this->assertInstanceOf(ImageManager::class, $manager); $manager = ImageManager::withDriver(Driver::class); $this->assertInstanceOf(ImageManager::class, $manager); } public function testDriver(): void { $driver = new Driver(); $manager = ImageManager::withDriver($driver); $this->assertEquals($driver, $manager->driver()); } public function testDriverStatic(): void { $manager = ImageManager::imagick(); $this->assertInstanceOf(ImageManager::class, $manager); } public function testCreate(): void { $manager = new ImageManager(Driver::class); $image = $manager->create(5, 4); $this->assertInstanceOf(ImageInterface::class, $image); } public function testAnimate(): void { $manager = new ImageManager(Driver::class); $image = $manager->animate(function ($animation): void { $animation->add($this->getTestResourcePath('red.gif'), .25); }); $this->assertInstanceOf(ImageInterface::class, $image); } public function testRead(): void { $manager = new ImageManager(Driver::class); $image = $manager->read($this->getTestResourcePath('red.gif')); $this->assertInstanceOf(ImageInterface::class, $image); } public function testReadWithDecoderClassname(): void { $manager = new ImageManager(Driver::class); $image = $manager->read($this->getTestResourcePath('red.gif'), FilePathImageDecoder::class); $this->assertInstanceOf(ImageInterface::class, $image); } public function testReadWithDecoderInstance(): void { $manager = new ImageManager(Driver::class); $image = $manager->read($this->getTestResourcePath('red.gif'), new FilePathImageDecoder()); $this->assertInstanceOf(ImageInterface::class, $image); } public function testReadWithDecoderClassnameArray(): void { $manager = new ImageManager(Driver::class); $image = $manager->read($this->getTestResourcePath('red.gif'), [FilePathImageDecoder::class]); $this->assertInstanceOf(ImageInterface::class, $image); } public function testReadWithDecoderInstanceArray(): void { $manager = new ImageManager(Driver::class); $image = $manager->read($this->getTestResourcePath('red.gif'), [new FilePathImageDecoder()]); $this->assertInstanceOf(ImageInterface::class, $image); } public function testReadWithDecoderInstanceArrayMultiple(): void { $manager = new ImageManager(Driver::class); $image = $manager->read($this->getTestResourcePath('red.gif'), [ new BinaryImageDecoder(), new FilePathImageDecoder(), ]); $this->assertInstanceOf(ImageInterface::class, $image); } public function testReadWithRotationAdjustment(): void { $manager = new ImageManager(Driver::class); $image = $manager->read($this->getTestResourcePath('orientation.jpg')); $this->assertColor(1, 0, 254, 255, $image->pickColor(3, 3)); } public function testReadWithoutRotationAdjustment(): void { $manager = new ImageManager(Driver::class, autoOrientation: false); $image = $manager->read($this->getTestResourcePath('orientation.jpg')); $this->assertColor(250, 2, 3, 255, $image->pickColor(3, 3)); } public function testReadAnimation(): void { $manager = new ImageManager(Driver::class); $image = $manager->read($this->getTestResourcePath('animation.gif')); $this->assertTrue($image->isAnimated()); } public function testReadAnimationDiscarded(): void { $manager = new ImageManager(Driver::class, decodeAnimation: false); $image = $manager->read($this->getTestResourcePath('animation.gif')); $this->assertFalse($image->isAnimated()); } public function testApplyBlendingColor(): void { $manager = new ImageManager(Driver::class); $image = $manager->read($this->getTestResourcePath('blocks.png')); $result = $image->blendTransparency(); $this->assertColor(255, 255, 255, 255, $image->pickColor(530, 0)); $this->assertColor(255, 255, 255, 255, $result->pickColor(530, 0)); } public function testApplyBlendingColorConfigured(): void { $manager = new ImageManager(Driver::class, blendingColor: 'ff5500'); $image = $manager->read($this->getTestResourcePath('blocks.png')); $result = $image->blendTransparency(); $this->assertColor(255, 85, 0, 255, $image->pickColor(530, 0)); $this->assertColor(255, 85, 0, 255, $result->pickColor(530, 0)); } } ================================================ FILE: tests/Unit/InputHandlerTest.php ================================================ assertInstanceOf($outputClassname, $handler->handle($input)); } else { $this->expectException($outputClassname); $handler->handle($input); } } public static function inputProvider(): Generator { $base = [ [null, DecoderException::class], ['', DecoderException::class], ['fff', ColorInterface::class], ['rgba(0, 0, 0, 0)', ColorInterface::class], ['cmyk(0, 0, 0, 0)', ColorInterface::class], ['hsv(0, 0, 0)', ColorInterface::class], ['hsl(0, 0, 0)', ColorInterface::class], ['transparent', ColorInterface::class], ['steelblue', ColorInterface::class], [self::getTestResourcePath(), ImageInterface::class], [file_get_contents(self::getTestResourcePath()), ImageInterface::class], ]; $drivers = [GdDriver::class, ImagickDriver::class]; foreach ($drivers as $driver) { foreach ($base as $line) { array_unshift($line, $driver); // prepend driver yield $line; } } } public function testResolveWithoutDriver(): void { $handler = new InputHandler([new HexColorDecoder()]); $result = $handler->handle('fff'); $this->assertInstanceOf(ColorInterface::class, $result); $handler = new InputHandler([HexColorDecoder::class]); $result = $handler->handle('fff'); $this->assertInstanceOf(ColorInterface::class, $result); } } ================================================ FILE: tests/Unit/MediaTypeTest.php ================================================ assertEquals(MediaType::IMAGE_JPEG, MediaType::create(MediaType::IMAGE_JPEG)); $this->assertEquals(MediaType::IMAGE_JPEG, MediaType::create(Format::JPEG)); $this->assertEquals(MediaType::IMAGE_JPEG, MediaType::create(FileExtension::JPG)); $this->assertEquals(MediaType::IMAGE_JPEG, MediaType::create('jpg')); $this->assertEquals(MediaType::IMAGE_JPEG, MediaType::create('jpeg')); $this->assertEquals(MediaType::IMAGE_JPEG, MediaType::create('image/jpeg')); $this->assertEquals(MediaType::IMAGE_JPEG, MediaType::create('JPG')); $this->assertEquals(MediaType::IMAGE_JPEG, MediaType::create('JPEG')); $this->assertEquals(MediaType::IMAGE_JPEG, MediaType::create('IMAGE/JPEG')); } public function testCreateUnknown(): void { $this->expectException(NotSupportedException::class); MediaType::create('foo'); } public function testTryCreate(): void { $this->assertEquals(MediaType::IMAGE_JPEG, MediaType::tryCreate(MediaType::IMAGE_JPEG)); $this->assertEquals(MediaType::IMAGE_JPEG, MediaType::tryCreate(Format::JPEG)); $this->assertEquals(MediaType::IMAGE_JPEG, MediaType::tryCreate(FileExtension::JPG)); $this->assertEquals(MediaType::IMAGE_JPEG, MediaType::tryCreate('jpg')); $this->assertEquals(MediaType::IMAGE_JPEG, MediaType::tryCreate('jpeg')); $this->assertEquals(MediaType::IMAGE_JPEG, MediaType::tryCreate('image/jpeg')); $this->assertNull(Format::tryCreate('no-format')); } public function testFormatJpeg(): void { $mime = MediaType::IMAGE_JPEG; $this->assertEquals(Format::JPEG, $mime->format()); $mime = MediaType::IMAGE_PJPEG; $this->assertEquals(Format::JPEG, $mime->format()); $mime = MediaType::IMAGE_JPG; $this->assertEquals(Format::JPEG, $mime->format()); $mime = MediaType::IMAGE_X_JPEG; $this->assertEquals(Format::JPEG, $mime->format()); } public function testFormatWebp(): void { $mime = MediaType::IMAGE_WEBP; $this->assertEquals(Format::WEBP, $mime->format()); $mime = MediaType::IMAGE_X_WEBP; $this->assertEquals(Format::WEBP, $mime->format()); } public function testFormatGif(): void { $mime = MediaType::IMAGE_GIF; $this->assertEquals(Format::GIF, $mime->format()); } public function testFormatPng(): void { $mime = MediaType::IMAGE_PNG; $this->assertEquals(Format::PNG, $mime->format()); $mime = MediaType::IMAGE_X_PNG; $this->assertEquals(Format::PNG, $mime->format()); } public function testFormatAvif(): void { $mime = MediaType::IMAGE_AVIF; $this->assertEquals(Format::AVIF, $mime->format()); $mime = MediaType::IMAGE_X_AVIF; $this->assertEquals(Format::AVIF, $mime->format()); } public function testFormatBmp(): void { $mime = MediaType::IMAGE_BMP; $this->assertEquals(Format::BMP, $mime->format()); $mime = MediaType::IMAGE_X_BMP; $this->assertEquals(Format::BMP, $mime->format()); $mime = MediaType::IMAGE_X_BITMAP; $this->assertEquals(Format::BMP, $mime->format()); $mime = MediaType::IMAGE_X_WIN_BITMAP; $this->assertEquals(Format::BMP, $mime->format()); $mime = MediaType::IMAGE_X_WINDOWS_BMP; $this->assertEquals(Format::BMP, $mime->format()); $mime = MediaType::IMAGE_X_BMP3; $this->assertEquals(Format::BMP, $mime->format()); } public function testFormatTiff(): void { $mime = MediaType::IMAGE_TIFF; $this->assertEquals(Format::TIFF, $mime->format()); } public function testFormatJpeg2000(): void { $mime = MediaType::IMAGE_JPM; $this->assertEquals(Format::JP2, $mime->format()); $mime = MediaType::IMAGE_JPX; $this->assertEquals(Format::JP2, $mime->format()); $mime = MediaType::IMAGE_JP2; $this->assertEquals(Format::JP2, $mime->format()); } public function testFormatHeic(): void { $mime = MediaType::IMAGE_HEIC; $this->assertEquals(Format::HEIC, $mime->format()); $mime = MediaType::IMAGE_X_HEIC; $this->assertEquals(Format::HEIC, $mime->format()); $mime = MediaType::IMAGE_HEIF; $this->assertEquals(Format::HEIC, $mime->format()); } #[DataProvider('fileExtensionsDataProvider')] public function testFileExtensions( MediaType $mediaType, int $fileExtensionCount, FileExtension $fileExtension ): void { $this->assertCount($fileExtensionCount, $mediaType->fileExtensions()); $this->assertEquals($fileExtension, $mediaType->fileExtension()); } public static function fileExtensionsDataProvider(): Generator { yield [MediaType::IMAGE_JPEG, 2, FileExtension::JPG]; yield [MediaType::IMAGE_JPG, 2, FileExtension::JPG]; yield [MediaType::IMAGE_PJPEG, 2, FileExtension::JPG]; yield [MediaType::IMAGE_X_JPEG, 2, FileExtension::JPG]; yield [MediaType::IMAGE_WEBP, 1, FileExtension::WEBP]; yield [MediaType::IMAGE_X_WEBP, 1, FileExtension::WEBP]; yield [MediaType::IMAGE_GIF, 1, FileExtension::GIF]; yield [MediaType::IMAGE_PNG, 1, FileExtension::PNG]; yield [MediaType::IMAGE_X_PNG, 1, FileExtension::PNG]; yield [MediaType::IMAGE_AVIF, 1, FileExtension::AVIF]; yield [MediaType::IMAGE_X_AVIF, 1, FileExtension::AVIF]; yield [MediaType::IMAGE_BMP, 1, FileExtension::BMP]; yield [MediaType::IMAGE_MS_BMP, 1, FileExtension::BMP]; yield [MediaType::IMAGE_X_BITMAP, 1, FileExtension::BMP]; yield [MediaType::IMAGE_X_BMP, 1, FileExtension::BMP]; yield [MediaType::IMAGE_X_MS_BMP, 1, FileExtension::BMP]; yield [MediaType::IMAGE_X_WINDOWS_BMP, 1, FileExtension::BMP]; yield [MediaType::IMAGE_X_WIN_BITMAP, 1, FileExtension::BMP]; yield [MediaType::IMAGE_X_XBITMAP, 1, FileExtension::BMP]; yield [MediaType::IMAGE_X_BMP3, 1, FileExtension::BMP]; yield [MediaType::IMAGE_TIFF, 2, FileExtension::TIF]; yield [MediaType::IMAGE_JP2, 9, FileExtension::JP2]; yield [MediaType::IMAGE_JPX, 9, FileExtension::JP2]; yield [MediaType::IMAGE_JPM, 9, FileExtension::JP2]; yield [MediaType::IMAGE_HEIC, 2, FileExtension::HEIC]; yield [MediaType::IMAGE_X_HEIC, 2, FileExtension::HEIC]; yield [MediaType::IMAGE_HEIF, 2, FileExtension::HEIC]; } } ================================================ FILE: tests/Unit/ModifierStackTest.php ================================================ assertInstanceOf(ModifierStack::class, $stack); } public function testPush(): void { $stack = new ModifierStack([]); $result = $stack->push(new GreyscaleModifier()); $this->assertInstanceOf(ModifierStack::class, $result); } public function testApply(): void { $image = Mockery::mock(ImageInterface::class); $modifier1 = Mockery::mock(ModifierInterface::class)->makePartial(); $modifier1->shouldReceive('apply')->once()->with($image); $modifier2 = Mockery::mock(ModifierInterface::class)->makePartial(); $modifier2->shouldReceive('apply')->once()->with($image); $stack = new ModifierStack([$modifier1, $modifier2]); $result = $stack->apply($image); $this->assertInstanceOf(ImageInterface::class, $result); } } ================================================ FILE: tests/Unit/Modifiers/ColorspaceModifierTest.php ================================================ assertInstanceOf(ColorspaceInterface::class, $modifier->targetColorspace()); $modifier = new ColorspaceModifier('rgb'); $this->assertInstanceOf(ColorspaceInterface::class, $modifier->targetColorspace()); $modifier = new ColorspaceModifier('cmyk'); $this->assertInstanceOf(ColorspaceInterface::class, $modifier->targetColorspace()); $modifier = new ColorspaceModifier('test'); $this->expectException(NotSupportedException::class); $modifier->targetColorspace(); } } ================================================ FILE: tests/Unit/Modifiers/PadModifierTest.php ================================================ shouldReceive('size')->andReturn($size); $this->assertInstanceOf(SizeInterface::class, $modifier->getCropSize($image)); } } ================================================ FILE: tests/Unit/Modifiers/RemoveAnimationModifierTest.php ================================================ makePartial(); $image->shouldReceive('count')->andReturn($frames); return $this->normalizePosition($image); } }; $this->assertEquals($normalized, $modifier->testResult($frames)); } public static function normalizePositionProvider(): Generator { yield [0, 100, 0]; yield [10, 100, 10]; yield ['10', 100, 10]; yield ['0%', 100, 0]; yield ['50%', 100, 50]; yield ['100%', 100, 99]; } } ================================================ FILE: tests/Unit/Modifiers/TextModifierTest.php ================================================ */ public function testStrokeOffsets(FontInterface $font): array { return $this->strokeOffsets($font); } }; $this->assertEquals([], $modifier->testStrokeOffsets(new Font())); $this->assertEquals([ new Point(-1, -1), new Point(-1, 0), new Point(-1, 1), new Point(0, -1), new Point(0, 0), new Point(0, 1), new Point(1, -1), new Point(1, 0), new Point(1, 1), ], $modifier->testStrokeOffsets((new Font())->setStrokeWidth(1))); } } ================================================ FILE: tests/Unit/OriginTest.php ================================================ getTestResourcePath('example.jpg')); $this->assertEquals($this->getTestResourcePath('example.jpg'), $origin->filePath()); } public function testFileExtension(): void { $origin = new Origin('image/jpeg', $this->getTestResourcePath('example.jpg')); $this->assertEquals('jpg', $origin->fileExtension()); $origin = new Origin('image/jpeg'); $this->assertEquals('', $origin->fileExtension()); } public function testSetGetMediaType(): void { $origin = new Origin(); $this->assertEquals('application/octet-stream', $origin->mediaType()); $origin = new Origin('image/gif'); $this->assertEquals('image/gif', $origin->mediaType()); $this->assertEquals('image/gif', $origin->mimetype()); $result = $origin->setMediaType('image/jpeg'); $this->assertEquals('image/jpeg', $origin->mediaType()); $this->assertEquals('image/jpeg', $result->mediaType()); } } ================================================ FILE: tests/Unit/ResolutionTest.php ================================================ assertInstanceOf(Resolution::class, $resolution); } public function testIteration(): void { $resolution = new Resolution(1.2, 3.4); foreach ($resolution as $value) { $this->assertIsFloat($value); } } public function testXY(): void { $resolution = new Resolution(1.2, 3.4); $this->assertEquals(1.2, $resolution->x()); $this->assertEquals(3.4, $resolution->y()); } public function testUnit(): void { $resolution = new Resolution(1, 1); $this->assertEquals('dpi', $resolution->unit()); $resolution = new Resolution(1, 1, Resolution::PER_CM); $this->assertEquals('dpcm', $resolution->unit()); } public function testConversion(): void { $resolution = new Resolution(300, 150); // per inch $this->assertEquals(300, $resolution->perInch()->x()); $this->assertEquals(150, $resolution->perInch()->y()); $resolution = new Resolution(300, 150); // per inch $this->assertEquals(118.11, round($resolution->perCm()->x(), 2)); $this->assertEquals(59.06, round($resolution->perCm()->y(), 2)); $resolution = new Resolution(118.11024, 59.06, Resolution::PER_CM); // per cm $this->assertEquals(300, round($resolution->perInch()->x())); $this->assertEquals(150, round($resolution->perInch()->y())); } public function testToString(): void { $resolution = new Resolution(300, 150, Resolution::PER_CM); $this->assertEquals('300.00 x 150.00 dpcm', $resolution->toString()); $resolution = new Resolution(300, 150, Resolution::PER_INCH); $this->assertEquals('300.00 x 150.00 dpi', $resolution->toString()); $this->assertEquals('300.00 x 150.00 dpi', (string) $resolution); } } ================================================ FILE: tests/Unit/Typography/FontFactoryTest.php ================================================ getTestResourcePath('test.ttf'); $factory = new FontFactory(new Font($font_file)); $result = $factory(); $this->assertInstanceOf(FontInterface::class, $result); $this->assertEquals($font_file, $result->filename()); } public function testBuildWithCallback(): void { $factory = new FontFactory(function (FontFactory $font): void { $font->filename($this->getTestResourcePath('test.ttf')); $font->color('#b01735'); $font->size(70); $font->align('center'); $font->valign('middle'); $font->lineHeight(1.6); $font->angle(10); $font->wrap(100); $font->stroke('ff5500', 4); }); $result = $factory(); $this->assertInstanceOf(FontInterface::class, $result); $this->assertEquals($this->getTestResourcePath('test.ttf'), $result->filename()); $this->assertEquals('#b01735', $result->color()); $this->assertEquals(70, $result->size()); $this->assertEquals('center', $result->alignment()); $this->assertEquals('middle', $result->valignment()); $this->assertEquals(1.6, $result->lineHeight()); $this->assertEquals(10, $result->angle()); $this->assertEquals(100, $result->wrapWidth()); $this->assertEquals(4, $result->strokeWidth()); $this->assertEquals('ff5500', $result->strokeColor()); } } ================================================ FILE: tests/Unit/Typography/FontTest.php ================================================ assertInstanceOf(Font::class, $font); $this->assertEquals('foo.ttf', $font->filename()); } public function testSetGetSize(): void { $font = new Font(); $this->assertEquals(12, $font->size()); $result = $font->setSize(123); $this->assertInstanceOf(Font::class, $result); $this->assertEquals(123, $font->size()); } public function testSetGetAngle(): void { $font = new Font(); $this->assertEquals(0, $font->angle()); $result = $font->setAngle(123); $this->assertInstanceOf(Font::class, $result); $this->assertEquals(123, $font->angle()); } public function testSetGetFilename(): void { $font = new Font(); $this->assertEquals(null, $font->filename()); $this->assertFalse($font->hasFilename()); $filename = $this->getTestResourcePath(); $result = $font->setFilename($filename); $this->assertTrue($font->hasFilename()); $this->assertInstanceOf(Font::class, $result); $this->assertEquals($filename, $font->filename()); } public function testSetGetColor(): void { $font = new Font(); $this->assertEquals('000000', $font->color()); $result = $font->setColor('fff'); $this->assertInstanceOf(Font::class, $result); $this->assertEquals('fff', $font->color()); } public function testSetGetAlignment(): void { $font = new Font(); $this->assertEquals('left', $font->alignment()); $result = $font->setAlignment('center'); $this->assertInstanceOf(Font::class, $result); $this->assertEquals('center', $font->alignment()); } public function testSetGetValignment(): void { $font = new Font(); $this->assertEquals('bottom', $font->valignment()); $result = $font->setValignment('center'); $this->assertInstanceOf(Font::class, $result); $this->assertEquals('center', $font->valignment()); } public function testSetGetLineHeight(): void { $font = new Font(); $this->assertEquals(1.25, $font->lineHeight()); $result = $font->setLineHeight(3.2); $this->assertInstanceOf(Font::class, $result); $this->assertEquals(3.2, $font->lineHeight()); } public function testSetGetStrokeColor(): void { $font = new Font(); $this->assertEquals('ffffff', $font->strokeColor()); $result = $font->setStrokeColor('000000'); $this->assertInstanceOf(Font::class, $result); $this->assertEquals('000000', $font->strokeColor()); } public function testSetGetStrokeWidth(): void { $font = new Font(); $this->assertEquals(0, $font->strokeWidth()); $result = $font->setStrokeWidth(4); $this->assertInstanceOf(Font::class, $result); $this->assertEquals(4, $font->strokeWidth()); } public function testSetStrokeWidthOutOfRange(): void { $font = new Font(); $this->expectException(FontException::class); $font->setStrokeWidth(11); } } ================================================ FILE: tests/Unit/Typography/LineTest.php ================================================ assertInstanceOf(Line::class, $line); } #[DataProvider('toStringDataProvider')] public function testToString(string $text, int $words): void { $line = new Line($text); $this->assertEquals($words, $line->count()); $this->assertEquals($text, (string) $line); } public function testSetGetPosition(): void { $line = new Line('foo'); $this->assertEquals(0, $line->position()->x()); $this->assertEquals(0, $line->position()->y()); $line->setPosition(new Point(10, 11)); $this->assertEquals(10, $line->position()->x()); $this->assertEquals(11, $line->position()->y()); } public function testCount(): void { $line = new Line(); $this->assertEquals(0, $line->count()); $line = new Line("foo"); $this->assertEquals(1, $line->count()); $line = new Line("foo bar"); $this->assertEquals(2, $line->count()); } public function testLength(): void { $line = new Line(); $this->assertEquals(0, $line->length()); $line = new Line("foo"); $this->assertEquals(3, $line->length()); $line = new Line("foo bar."); $this->assertEquals(8, $line->length()); $line = new Line("🫷🙂🫸"); $this->assertEquals(3, $line->length()); } public function testAdd(): void { $line = new Line(); $this->assertEquals(0, $line->count()); $result = $line->add('foo'); $this->assertEquals(1, $line->count()); $this->assertEquals(1, $result->count()); $result = $line->add('bar'); $this->assertEquals(2, $line->count()); $this->assertEquals(2, $result->count()); } public static function toStringDataProvider(): Generator { yield ['foo', 1]; yield ['foo bar', 2]; yield ['测试', 2]; // CJK Unified Ideographs yield ['テスト', 3]; // japanese yield ['ทดสอบ', 5]; // thai yield ['这只是我写的一个测试。', 11]; // CJK Unified Ideographs } } ================================================ FILE: tests/Unit/Typography/TextBlockTest.php ================================================ block = new TextBlock(<<assertEquals(3, $this->block->count()); } public function testLines(): void { $this->assertCount(3, $this->block->lines()); } public function testGetLine(): void { $this->assertEquals('foo', $this->block->line(0)); $this->assertEquals('FooBar', $this->block->line(1)); $this->assertEquals('bar', $this->block->line(2)); $this->assertNull($this->block->line(20)); } public function testLongestLine(): void { $result = $this->block->longestLine(); $this->assertEquals('FooBar', (string) $result); } }