withData(['width' => $width]); } if ($height <= 0) { throw FrameworkException::create( ErrorCode::VAL_INVALID_FORMAT, 'Height must be greater than 0' )->withData(['height' => $height]); } } /** * Create from image file */ public static function fromImageFile(string $filepath): self { $imageInfo = getimagesize($filepath); if ($imageInfo === false) { throw FrameworkException::create( ErrorCode::VAL_INVALID_FORMAT, 'Could not read dimensions from image file' )->withData(['filepath' => $filepath]); } return new self($imageInfo[0], $imageInfo[1]); } /** * Create from array [width, height] */ public static function fromArray(array $data): self { if (count($data) !== 2) { throw FrameworkException::create( ErrorCode::VAL_INVALID_FORMAT, 'Dimensions array must contain exactly 2 elements [width, height]' )->withData(['data' => $data]); } return new self((int) $data[0], (int) $data[1]); } /** * Calculate aspect ratio */ public function getAspectRatio(): float { return $this->width / $this->height; } /** * Get orientation */ public function getOrientation(): Orientation { if ($this->height > $this->width) { return Orientation::PORTRAIT; } if ($this->width > $this->height) { return Orientation::LANDSCAPE; } return Orientation::SQUARE; } /** * Check if dimensions are portrait orientation */ public function isPortrait(): bool { return $this->getOrientation() === Orientation::PORTRAIT; } /** * Check if dimensions are landscape orientation */ public function isLandscape(): bool { return $this->getOrientation() === Orientation::LANDSCAPE; } /** * Check if dimensions are square */ public function isSquare(): bool { return $this->getOrientation() === Orientation::SQUARE; } /** * Get total area (width * height) */ public function getArea(): int { return $this->width * $this->height; } /** * Scale dimensions proportionally to fit within max dimensions */ public function scaleToFit(int $maxWidth, int $maxHeight): self { $ratio = min($maxWidth / $this->width, $maxHeight / $this->height); if ($ratio >= 1) { return $this; // Already fits } return new self( (int) round($this->width * $ratio), (int) round($this->height * $ratio) ); } /** * Scale dimensions proportionally to fill max dimensions (may crop) */ public function scaleToFill(int $maxWidth, int $maxHeight): self { $ratio = max($maxWidth / $this->width, $maxHeight / $this->height); return new self( (int) round($this->width * $ratio), (int) round($this->height * $ratio) ); } /** * Scale by factor */ public function scaleBy(float $factor): self { if ($factor <= 0) { throw FrameworkException::create( ErrorCode::VAL_INVALID_FORMAT, 'Scale factor must be greater than 0' )->withData(['factor' => $factor]); } return new self( (int) round($this->width * $factor), (int) round($this->height * $factor) ); } /** * Check if dimensions match */ public function equals(self $other): bool { return $this->width === $other->width && $this->height === $other->height; } /** * Convert to array */ public function toArray(): array { return [ 'width' => $this->width, 'height' => $this->height, 'aspect_ratio' => $this->getAspectRatio(), 'orientation' => $this->getOrientation()->value, 'area' => $this->getArea(), ]; } /** * String representation (e.g., "1920x1080") */ public function __toString(): string { return "{$this->width}x{$this->height}"; } }