2012-08-15 2 views
4

Je dois créer une commande qui imite le mode de fusion "Couleur" d'Adobe Photoshop dans ImageMagick afin de teinter une image. Pour ce faire, j'essaye de composer l'image originale et une autre image constituée d'une couche de couleur, à 35% d'opacité. Cela devrait se fondre avec l'image originale et créer une image de résultat colorée.Dupliquer le mode de fusion "Couleur" de Photoshop dans ImageMagick

Ceci est le résultat attendu: the expected result

Le mode de fusion « couleur » est définie, sur le site d'Adobe, comme ceci: « Crée une couleur résultat avec la luminance de la couleur de base et la teinte et saturation de la couleur de fusion, ce qui préserve les niveaux de gris dans l'image et est utile pour colorier des images monochromes et colorer des images en couleur. "

Il y a une méthode de composition définie dans ImageMagick qui semble faire la même chose (Luminize), mais les résultats ne sont pas de loin ce qui est attendu.

Ce qui semble donner le plus proche résultat dans ImageMagick est la méthode mélange par défaut de composition, utilisé comme ceci:

convert image.jpg color_layer.png -compose blend -composite result.jpg 

J'ai aussi essayé de créer une image qui contiendrait la luminosité de la première image et la teinte et saturation de la seconde en utilisant l'opérateur -fx, mais le résultat était encore loin de ce dont j'avais besoin.

+0

Comment êtes-vous rendre compte de la "opacité de 35%" dans vos commandes ImageMagick? – DarenW

+0

Afin d'obtenir cela à l'écart, j'ai créé un fichier png transparent avec la couleur et réduit l'opacité à 35%. Donc, j'essaie pratiquement de mélanger deux fichiers image existants. – viorel

+0

Pouvez-vous fournir les images suivantes: (1) image originale; (2) image de couche de couleur à une opacité de 35%; (3) image du résultat attendu (ce que crée photoshop) –

Répondre

3

Sur la base de Castles réponse précieuse, j'ai essayé de trouver la meilleure solution de le faire en PHP. L'implémentation qu'il a citée a deux défauts majeurs: l'un qui ne tient pas compte de l'opacité, le cas échéant, et l'autre qui est très lent et consomme beaucoup de ressources. Le traitement d'une image de 500x500 pixels en PHP prendrait environ 15 secondes, ce qui permettrait à Apache de maintenir le processeur à 95%.

La ressource la plus rapide et la moins gourmande que j'ai trouvée était en fait en HTML5 en utilisant canvas pour traiter l'image. Les résultats sont incroyables et l'image est en cours de traitement.

Je posterai ci-dessous les derniers morceaux de code, un pour PHP et un pour HTML. Si vous avez besoin d'utiliser ce côté serveur, vous pouvez copier-coller le code HTML dans le nœud.js et NodeCanvas: https://github.com/LearnBoost/node-canvas

PHP (avec l'opacité):

<?php 

function Lum($colour) { 
    return ($colour['r'] * 0.3) + ($colour['g'] * 0.59) + ($colour['b'] * 0.11); 
} 

function ClipColour($colour) { 
    $result  = $colour; 
    $luminance = Lum($colour); 

    $cMin = min($colour['r'], $colour['g'], $colour['b']); 
    $cMax = max($colour['r'], $colour['g'], $colour['b']); 

    if ($cMin < 0.0) { 
     $result['r'] = $luminance + ((($colour['r'] - $luminance) * $luminance)/($luminance - $cMin)); 
     $result['g'] = $luminance + ((($colour['g'] - $luminance) * $luminance)/($luminance - $cMin)); 
     $result['b'] = $luminance + ((($colour['b'] - $luminance) * $luminance)/($luminance - $cMin)); 
    } 

    if ($cMax > 255) { 
     $result['r'] = $luminance + ((($colour['r'] - $luminance) * (255 - $luminance))/($cMax - $luminance)); 
     $result['g'] = $luminance + ((($colour['g'] - $luminance) * (255 - $luminance))/($cMax - $luminance)); 
     $result['b'] = $luminance + ((($colour['b'] - $luminance) * (255 - $luminance))/($cMax - $luminance)); 
    } 

    return $result; 
} 

function SetLum($colour, $luminance) { 

    $result = array(); 

    $diff = $luminance - Lum($colour); 

    $result['r'] = $colour['r'] + $diff; 
    $result['g'] = $colour['g'] + $diff; 
    $result['b'] = $colour['b'] + $diff; 

    return ClipColour($result); 

} 

function normalizeColor($color) { 
    $color['r'] = $color['r']/255; 
    $color['g'] = $color['g']/255; 
    $color['b'] = $color['b']/255; 

    return $color; 
} 

function denormalizeColor($color) { 
    $color['r'] = round($color['r'] * 255); 
    $color['g'] = round($color['g'] * 255); 
    $color['b'] = round($color['b'] * 255); 

    return $color; 
} 

$overlay_color = array('r'=>180,'g'=>22,'b'=>1, 'a' => 0.35); 

$img = new Imagick(); 

if(!isset($_GET['case'])) { 
    $_GET['case'] = ''; 
} 

//unmodified version 
$original = new Imagick('girl.jpg'); 

//photoshop image to compare 
$ps = new Imagick('original.jpg'); 

$img->addImage($original); 
$it = $original->getPixelIterator(); 

foreach($it as $row => $pixels) { 
    foreach ($pixels as $column => $pixel) { 
     $rgbIni = $pixel->getColor(); 

     $rgb = SetLum($overlay_color, Lum($rgbIni)); 
     $overlay_color  = normalizeColor($overlay_color); 
      $rgb  = normalizeColor($rgb); 

      $rgbIni   = normalizeColor($rgbIni); 

     $rgb['r'] = ((1 - $overlay_color['a']) * $rgbIni['r']) + ($overlay_color['a'] * $rgb['r']); 
     $rgb['g'] = ((1 - $overlay_color['a']) * $rgbIni['g']) + ($overlay_color['a'] * $rgb['g']); 
     $rgb['b'] = ((1 - $overlay_color['a']) * $rgbIni['b']) + ($overlay_color['a'] * $rgb['b']); 

     $test   = denormalizeColor($test); 
     $rgb   = denormalizeColor($rgb); 
     $overlay_color = denormalizeColor($overlay_color); 

     $pixel->setColor('rgb('.round($rgb['r']).','. round($rgb['g']).','.round($rgb['b']).')'); 

    } 

    $it->syncIterator(); 
} 

//add modified version 
$img->addImage($original); 
$img->addImage($ps); 

$img->resetIterator(); 
$combined = $img->appendImages(true); //stack images 

header('content-type: image/jpeg'); 

$combined->setImageFormat("jpeg"); 

echo $combined; 

?> 

HTML:

<!DOCTYPE html> 
    <html lang="en"> 
    <head> 
    <meta charset="UTF-8" /> 
    <script> 
     var RGBA = function(r, g, b, a) { 
      this.R = r || 0; 
      this.G = g || 0; 
      this.B = b || 0; 
      this.A = a || 0.5; 
     } 

     function SetLum(initialColor, pixelColor) { 

      var initalColorLuminance = initialColor.R * 0.3 + initialColor.G * 0.59 + initialColor.B * 0.11; 
      var pixelColorLuminance = pixelColor.R * 0.3 + pixelColor.G * 0.59 + pixelColor.B * 0.11; 

      var diff = pixelColorLuminance - initalColorLuminance; 

      var response = new Array; 
       response[0] = initialColor.R + diff; 
       response[1] = initialColor.G + diff; 
       response[2] = initialColor.B + diff; 

      //console.log(response[0]); 

      return ClipColour(response); 

     } 

     function alphaComposite(mv, ov, a) { 
      return (mv * a) + (ov * (1 - a)); 
     } 

     function ClipColour(color) { //function to prevent underexposure or overexposure on some pixels 

      var result  = color; 
      var luminance = color[0] * 0.3 + color[1] * 0.59 + color[1] * 0.11; 

      var cMin = Math.min(color[0], color[1], color[2]); 
      var cMax = Math.max(color[0], color[1], color[2]); 

      if (cMin < 0.0) { 
       color[0] = luminance + (((color[0] - luminance) * luminance)/(luminance - cMin)); 
       color[1] = luminance + (((color[1] - luminance) * luminance)/(luminance - cMin)); 
       color[2] = luminance + (((color[2] - luminance) * luminance)/(luminance - cMin)); 
      } 

      if (cMax > 255) { 
       color[0] = luminance + (((color[0] - luminance) * (255 - luminance))/(cMax - luminance)); 
       color[1] = luminance + (((color[1] - luminance) * (255 - luminance))/(cMax - luminance)); 
       color[2] = luminance + (((color[2] - luminance) * (255 - luminance))/(cMax - luminance)); 
      } 

      return color; 
     } 

     function processImage(image, targetColour) { 
      var canvas = document.createElement('canvas'); 
       c = canvas.getContext('2d'); 

      canvas.width = image.width; 
      canvas.height = image.height; 

      // Draw the building on the original canvas 
      c.drawImage(image, 0, 0, canvas.width, canvas.height); 

      // There's a (much) faster way to cycle through all the pixels using typed arrays, 
      // but I'm playing it safe so that the example works in all browsers. 
      var imageData = c.getImageData(0, 0, canvas.width, canvas.height), 
       imageDataPixels = imageData.data; 

      for (var i = 0, len = imageDataPixels.length; i < len; i += 4) { 
       var pixelColor = new RGBA(imageDataPixels[i], imageDataPixels[i+1], imageDataPixels[i+2], 1); 
       var test = SetLum(targetColour, pixelColor); 

       var r = Math.round(test[0]); 
       var g = Math.round(test[1]); 
       var b = Math.round(test[2]); 

       imageDataPixels[i] = alphaComposite(r, imageDataPixels[i], targetColour.A); 
       imageDataPixels[i + 1] = alphaComposite(g, imageDataPixels[i + 1], targetColour.A); 
       imageDataPixels[i + 2] = alphaComposite(b, imageDataPixels[i + 2], targetColour.A); 
      } 

      c.putImageData(imageData, 0, 0); 

      return canvas; 
     } 

     document.addEventListener('DOMContentLoaded', function() { 
      var image = new Image(), 
       processImageFile = null; 

      image.src = "girl.jpg"; 

      image.addEventListener('load', function() { 
       var canvas = document.getElementById('canvas'), 
        c = canvas.getContext('2d'), 
        imageRGBA = new RGBA(180, 22, 1, 0.35); 

       canvas.width = image.width; 
       canvas.height = image.height; 

       c.drawImage(image, 0, 0); 

       processImageFile = processImage(image, imageRGBA); 
       c.drawImage(processImageFile, 0, 0); 
      }); 
     }); 
    </script> 
</head> 
<body> 

    <img src="girl.jpg" /> 
    <br /> 

    <canvas id="canvas"></canvas> 

    <br /> 
    <img src="original.jpg" /> 
</body> 

2

J'ai essayé de faire aussi bien et le meilleur que je suis venu avec est utiliser un filtre overlay .. Voici mon code en php:

$overlay_color = array('r'=>180,'g'=>22,'b'=>1); 

function overlay ($top, $bottom) { 
    return $bottom < 128 ? (2 * $bottom * $top)/255 : 255 - (2 * (255 - $bottom) * (255 - $top)/255); 
} 

$original = new Imagick('img/girl.jpg'); 

$img = new Imagick(); 

//unmodified version 
$img->addImage($original); 


$it = $original->getPixelIterator(); 

foreach($it as $row => $pixels) 
{ 
    foreach ($pixels as $column => $pixel) 
    { 
      $rgba = $pixel->getColor(); 
      $pixel->setColor('rgb('.overlay($overlay_color['r'], $rgba['r']).','.overlay($overlay_color['g'], $rgba['g']).','.overlay($overlay_color['b'], $rgba['b']).')'); 
    } 

    $it->syncIterator(); 
} 

//add modified version 
$img->addImage($original); 


$img->resetIterator(); 
$combined = $img->appendImages(true); //stack images 


header('content-type: image/jpeg'); 

$combined->setImageFormat("jpeg"); 

echo $combined; 

Je ne peux pas sembler pour trouver la formule pour le mode "couleur" Photoshop. Si je pouvais trouver que ce serait assez simple.

Mise à jour: J'ai trouvé ce site qui explique assez bien la formule Photoshop actuelle: http://www.beneaththewaves.net/Photography/Secrets_of_Photoshops_Colour_Blend_Mode_Revealed_Sort_Of.html et j'ai réussi à le faire fonctionner en PHP. Voici les fonctions:

function Lum($colour) { 

return ($colour['r'] * 0.3) + ($colour['g'] * 0.59) + ($colour['b'] * 0.11); 

} 



function ClipColour($colour) { 

    $result = $colour; 

    $luminance = Lum($colour); 

    $cMin = min($colour['r'], $colour['g'], $colour['b']); 

    $cMax = max($colour['r'], $colour['g'], $colour['b']); 

    if ($cMin < 0.0) { 

     $result['r'] = $luminance + ((($colour['r'] - $luminance) * $luminance)/($luminance - $cMin)); 

     $result['g'] = $luminance + ((($colour['g'] - $luminance) * $luminance)/($luminance - $cMin)); 

     $result['b'] = $luminance + ((($colour['b'] - $luminance) * $luminance)/($luminance - $cMin)); 


    } 

    if ($cMax > 255) { 

     $result['r'] = $luminance + ((($colour['r'] - $luminance) * (255 - $luminance))/($cMax - $luminance)); 

     $result['g'] = $luminance + ((($colour['g'] - $luminance) * (255 - $luminance))/($cMax - $luminance)); 

     $result['b'] = $luminance + ((($colour['b'] - $luminance) * (255 - $luminance))/($cMax - $luminance)); 

    } 

    return $result; 
} 



function SetLum($colour, $luminance) { 

    $result = array(); 

    $diff = $luminance - Lum($colour); 


    $result['r'] = $colour['r'] + $diff; 

    $result['g'] = $colour['g'] + $diff; 

    $result['b'] = $colour['b'] + $diff; 


    return ClipColour($result); 

} 

et est ici le code de conversion de pixel mis à jour:

$rgb = $pixel->getColor(); 
$rgb = SetLum($overlay_color,Lum($rgb)); 
$pixel->setColor('rgb('.round($rgb['r']).','. round($rgb['g']).','.round($rgb['b']).')'); 
+1

Bonjour Châteaux, cela semble effectivement faire l'affaire pour les images RVB (pas de RGBA) . J'ai aussi réussi à ajouter le bit de transparence (ma couleur a un alpha de 0.35). Vous pouvez trouver le code ici: http://pastebin.com/U1g5X16z. Il semble que j'ai eu de meilleurs résultats quand j'ai appliqué la transparence après l'assemblage et pas avant. Ma logique serait que la transparence devrait être ajoutée avant le mélange réel. Cependant, il y a un gros problème avec ce code: il est vraiment lent et Apache utilise 99,5% du processeur pendant son exécution. – viorel

+1

J'ai aussi traduit ceci à Imagemagick, mais le résultat est loin d'être ce qu'il devrait être. Peut-être avez-vous des idées? convertir original.jpg transparent_layer.png -canal RGBA -fx "diff = (ur * 0.3 + ug * 0.59 + ub * 0.11) - (vr * 0.3 + vg * 0.59 + vb * 0.11); v + diff" résultat .jpg Note: Altoughe Imagemagick est capable d'obtenir la luminance seule, elle n'utilise pas la même formule et vous ne pouvez pas faire la soustraction car elle génère une erreur – viorel

Questions connexes