base_image = $from_file; parent::__construct($pool, $config); } /** * @param string $file * * @return void * @throws \ImagickException * @throws \Exception */ public function saveTo($file) { $this->prepareImage(); $this->current_image->writeImage($file); } /** * */ public function display() { try { $this->prepareImage(); header('Content-type: image/jpeg'); echo $this->current_image; } catch (\ImagickException $e) { echo $e->getMessage(); } } /** * @return \Imagick * @throws \ImagickException */ public function superpose() { $file = $this->pool->getImage(); $this->files[] = $file; return $this->superposeOneImage($this->current_image, new \Imagick($file)); } /** * Superposes one image in the current image. * This method must only be called by the assembler_superpose thread ! * * @param \Imagick $current_image * @param \Imagick $image_to_superpose * * @return \Imagick * @throws \Exception */ private function superposeOneImage($current_image, $image_to_superpose) { if ($this->config->get('assembler.superpose.variante', 0) === 1) { $current_image->brightnessContrastImage(0.99, 1); } if ($image_to_superpose->getColorspace() !== \Imagick::COLORSPACE_RGB) { $image_to_superpose->setColorspace(\Imagick::COLORSPACE_RGB); } # If the image is bigger than current image, scale it down to 1/2 of final picture dimensions # (while keeping its ratio) $image_x = $image_to_superpose->getImageWidth(); $image_y = $image_to_superpose->getImageHeight(); if ($image_x < 32 || $image_y < 32) { throw new \Exception('Image too small.'); } if ($image_x > $this->config->get('assembler.sizex') || $image_y > $this->config->get('assembler.sizey')) { try { $image_to_superpose->scaleImage($this->config->get('assembler.sizex') / 2, $this->config->get('assembler.sizey') / 2, true); } catch (\ImagickException $e) { } } # Scale down/up image if required. $scale_value = $this->config->get('assembler.superpose.scale', 1); if ($scale_value !== 1) { $image_to_superpose->thumbnailImage($image_x * $scale_value, $image_y * $scale_value); } $image_x = $image_to_superpose->getImageWidth(); $image_y = $image_to_superpose->getImageHeight(); # Some image are too white. # For example, the photo of a coin on a white background. # These picture degrad the quality of the final image. # We try to dectect them by summing the value of the pixels # on the borders. # If the image is considered "white", we invert it. $pixel_count = 1; // to prevent divide by zero error. $value_count = 0; for ($x = 0; $x < $image_x; $x += 20) { $pixel = $image_to_superpose->getImagePixelColor($x, 5); $value_count += array_sum($pixel->getColor()); $pixel = $image_to_superpose->getImagePixelColor($x, $image_y - 5); $value_count += array_sum($pixel->getColor()); $pixel_count += 2; } for ($y = 0; $y < $image_y; $y += 20) { $pixel = $image_to_superpose->getImagePixelColor(5, $y); $value_count += array_sum($pixel->getColor()); $pixel = $image_to_superpose->getImagePixelColor($image_x - 5, $y); $value_count += array_sum($pixel->getColor()); $pixel_count += 2; } # If the average r+g+b of the border pixels exceed this value, # we consider the image is too white, and we invert it. if (100 * $value_count / (255 * 3) / $pixel_count > 60) { $image_to_superpose->negateImage(false); } $paste_coords_x = random_int(-($image_x/2), max(0, $this->config->get('assembler.sizex')-$image_x)); $paste_coords_y = random_int(-($image_y/2), max(0, $this->config->get('assembler.sizey')-$image_y)); # Darken image borders $image_to_superpose = $this->darkenImageBorder($image_to_superpose); if ($this->config->get('assembler.superpose.randomrotation', false)) { $image_to_superpose->rotateImage('none', random_int(0, 359)); $image_to_superpose = $this->darkenImageBorder($image_to_superpose); } // help for mask : https://phpimagick.com/Tutorial/backgroundMasking $image_to_superpose->setImageMatte(true); $mask_image = clone $image_to_superpose; $mask_image->setImageType(Imagick::IMGTYPE_GRAYSCALE); $mask_image->transformImageColorSpace(Imagick::COLORSPACE_GRAY); # Invert the transparency of 5% of the images (Except if we are in variante 1 mode) if ($this->config->get('assembler.superpose.variante') === 1 && random_int(0, 100) < 5) { $mask_image->negateImage(true); } $image_to_superpose->compositeImage($mask_image, \Imagick::COMPOSITE_COPYOPACITY, 0, 0); $current_image->compositeImage($image_to_superpose, \Imagick::COMPOSITE_DEFAULT, $paste_coords_x, $paste_coords_y); if ($this->config->get('assembler.superpose.variante') === 0) { $current_image->equalizeImage(); } else { $current_image->autoLevelImage(); } return $current_image; } /** * @param \Imagick $image * @param int|null $border_size * * @return \Imagick */ private function darkenImageBorder(Imagick $image, $border_size = null) { if (null === $border_size) { $border_size = $this->config->get('assembler.superpose.bordersmooth'); } $image_x = $image->getImageWidth(); $image_y = $image->getImageHeight(); for ($i = 0; $i < $border_size; $i++) { $draw = new \ImagickDraw(); $draw->setStrokeOpacity(($border_size-$i)/$border_size); $draw->setStrokeWidth(1); $draw->setStrokeColor('black'); $draw->setFillColor('none'); $draw->rectangle($i, $i, $image_x - $i, $image_y - $i); $image->drawImage($draw); } return $image; } /** * @throws \ImagickException */ private function prepareImage() { $this->current_image = null === $this->base_image ? new \Imagick() : new \Imagick($this->base_image); $this->current_image->setSize($this->config->get('assembler.sizex'), $this->config->get('assembler.sizey')); $this->current_image->newImage($this->config->get('assembler.sizex'), $this->config->get('assembler.sizey'), 'none'); $nb_images = $this->config->get('assembler.superpose.min_num_images', 5); for ($i = 0; $i < $nb_images; $i++) { $this->current_image = $this->superpose(); } } }