paint.c 37.8 KB
Newer Older
cristy's avatar
cristy committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                                                                             %
%                                                                             %
%                                                                             %
%                      PPPP    AAA   IIIII  N   N  TTTTT                      %
%                      P   P  A   A    I    NN  N    T                        %
%                      PPPP   AAAAA    I    N N N    T                        %
%                      P      A   A    I    N  NN    T                        %
%                      P      A   A  IIIII  N   N    T                        %
%                                                                             %
%                                                                             %
%                        Methods to Paint on an Image                         %
%                                                                             %
%                              Software Design                                %
%                                John Cristy                                  %
%                                 July 1998                                   %
%                                                                             %
%                                                                             %
cristy's avatar
cristy committed
20
%  Copyright 1999-2013 ImageMagick Studio LLC, a non-profit organization      %
cristy's avatar
cristy committed
21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
%  dedicated to making software imaging solutions freely available.           %
%                                                                             %
%  You may not use this file except in compliance with the License.  You may  %
%  obtain a copy of the License at                                            %
%                                                                             %
%    http://www.imagemagick.org/script/license.php                            %
%                                                                             %
%  Unless required by applicable law or agreed to in writing, software        %
%  distributed under the License is distributed on an "AS IS" BASIS,          %
%  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   %
%  See the License for the specific language governing permissions and        %
%  limitations under the License.                                             %
%                                                                             %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
%
*/

/*
 Include declarations.
*/
#include "magick/studio.h"
cristy's avatar
cristy committed
43 44
#include "magick/cache.h"
#include "magick/channel.h"
cristy's avatar
cristy committed
45 46 47 48 49 50 51 52 53 54 55 56 57
#include "magick/color-private.h"
#include "magick/colorspace-private.h"
#include "magick/composite.h"
#include "magick/composite-private.h"
#include "magick/draw.h"
#include "magick/draw-private.h"
#include "magick/exception.h"
#include "magick/exception-private.h"
#include "magick/gem.h"
#include "magick/monitor.h"
#include "magick/monitor-private.h"
#include "magick/paint.h"
#include "magick/pixel-private.h"
cristy's avatar
cristy committed
58
#include "magick/resource_.h"
cristy's avatar
cristy committed
59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88
#include "magick/string_.h"
#include "magick/thread-private.h"

/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                                                                             %
%                                                                             %
%                                                                             %
%   F l o o d f i l l P a i n t I m a g e                                     %
%                                                                             %
%                                                                             %
%                                                                             %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
%  FloodfillPaintImage() changes the color value of any pixel that matches
%  target and is an immediate neighbor.  If the method FillToBorderMethod is
%  specified, the color value is changed for any neighbor pixel that does not
%  match the bordercolor member of image.
%
%  By default target must match a particular pixel color exactly.
%  However, in many cases two colors may differ by a small amount.  The
%  fuzz member of image defines how much tolerance is acceptable to
%  consider two colors as the same.  For example, set fuzz to 10 and the
%  color red at intensities of 100 and 102 respectively are now
%  interpreted as the same color for the purposes of the floodfill.
%
%  The format of the FloodfillPaintImage method is:
%
%      MagickBooleanType FloodfillPaintImage(Image *image,
%        const ChannelType channel,const DrawInfo *draw_info,
cristy's avatar
cristy committed
89 90
%        const MagickPixelPacket target,const ssize_t x_offset,
%        const ssize_t y_offset,const MagickBooleanType invert)
cristy's avatar
cristy committed
91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108
%
%  A description of each parameter follows:
%
%    o image: the image.
%
%    o channel: the channel(s).
%
%    o draw_info: the draw info.
%
%    o target: the RGB value of the target color.
%
%    o x_offset,y_offset: the starting location of the operation.
%
%    o invert: paint any pixel that does not match the target color.
%
*/
MagickExport MagickBooleanType FloodfillPaintImage(Image *image,
  const ChannelType channel,const DrawInfo *draw_info,
cristy's avatar
cristy committed
109
  const MagickPixelPacket *target,const ssize_t x_offset,const ssize_t y_offset,
cristy's avatar
cristy committed
110 111
  const MagickBooleanType invert)
{
cristy's avatar
cristy committed
112
#define MaxStacksize  131072UL
cristy's avatar
cristy committed
113 114 115 116 117 118
#define PushSegmentStack(up,left,right,delta) \
{ \
  if (s >= (segment_stack+MaxStacksize)) \
    ThrowBinaryException(DrawError,"SegmentStackOverflow",image->filename) \
  else \
    { \
cristy's avatar
cristy committed
119
      if ((((up)+(delta)) >= 0) && (((up)+(delta)) < (ssize_t) image->rows)) \
cristy's avatar
cristy committed
120 121 122 123 124 125 126 127 128 129
        { \
          s->x1=(double) (left); \
          s->y1=(double) (up); \
          s->x2=(double) (right); \
          s->y2=(double) (delta); \
          s++; \
        } \
    } \
}

cristy's avatar
cristy committed
130 131 132 133
  CacheView
    *floodplane_view,
    *image_view;

cristy's avatar
cristy committed
134 135 136 137 138 139 140 141 142 143 144 145 146
  ExceptionInfo
    *exception;

  Image
    *floodplane_image;

  MagickBooleanType
    skip;

  MagickPixelPacket
    fill,
    pixel;

cristy's avatar
cristy committed
147 148 149
  MemoryInfo
    *segment_info;

cristy's avatar
cristy committed
150 151 152 153 154 155 156 157 158
  PixelPacket
    fill_color;

  register SegmentInfo
    *s;

  SegmentInfo
    *segment_stack;

cristy's avatar
cristy committed
159 160 161 162 163 164 165 166
  ssize_t
    offset,
    start,
    x,
    x1,
    x2,
    y;

cristy's avatar
cristy committed
167 168 169 170 171 172 173 174 175
  /*
    Check boundary conditions.
  */
  assert(image != (Image *) NULL);
  assert(image->signature == MagickSignature);
  if (image->debug != MagickFalse)
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
  assert(draw_info != (DrawInfo *) NULL);
  assert(draw_info->signature == MagickSignature);
cristy's avatar
cristy committed
176
  if ((x_offset < 0) || (x_offset >= (ssize_t) image->columns))
cristy's avatar
cristy committed
177
    return(MagickFalse);
cristy's avatar
cristy committed
178
  if ((y_offset < 0) || (y_offset >= (ssize_t) image->rows))
cristy's avatar
cristy committed
179 180 181
    return(MagickFalse);
  if (SetImageStorageClass(image,DirectClass) == MagickFalse)
    return(MagickFalse);
cristy's avatar
cristy committed
182
  if (IsGrayColorspace(image->colorspace) != MagickFalse)
cristy's avatar
cristy committed
183
    (void) SetImageColorspace(image,sRGBColorspace);
cristy's avatar
cristy committed
184 185
  if ((image->matte == MagickFalse) &&
      (draw_info->fill.opacity != OpaqueOpacity))
cristy's avatar
cristy committed
186
    (void) SetImageAlphaChannel(image,OpaqueAlphaChannel);
cristy's avatar
cristy committed
187 188 189 190 191 192 193
  /*
    Set floodfill state.
  */
  floodplane_image=CloneImage(image,0,0,MagickTrue,&image->exception);
  if (floodplane_image == (Image *) NULL)
    return(MagickFalse);
  (void) SetImageAlphaChannel(floodplane_image,OpaqueAlphaChannel);
cristy's avatar
cristy committed
194 195
  segment_info=AcquireVirtualMemory(MaxStacksize,sizeof(*segment_stack));
  if (segment_info == (MemoryInfo *) NULL)
cristy's avatar
cristy committed
196 197 198 199 200
    {
      floodplane_image=DestroyImage(floodplane_image);
      ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
        image->filename);
    }
cristy's avatar
cristy committed
201
  segment_stack=(SegmentInfo *) GetVirtualMemoryBlob(segment_info);
cristy's avatar
cristy committed
202 203 204 205 206 207 208 209 210 211 212 213
  /*
    Push initial segment on stack.
  */
  exception=(&image->exception);
  x=x_offset;
  y=y_offset;
  start=0;
  s=segment_stack;
  PushSegmentStack(y,x,x,1);
  PushSegmentStack(y+1,x,x,-1);
  GetMagickPixelPacket(image,&fill);
  GetMagickPixelPacket(image,&pixel);
cristy's avatar
cristy committed
214 215
  image_view=AcquireVirtualCacheView(image,exception);
  floodplane_view=AcquireAuthenticCacheView(floodplane_image,exception);
cristy's avatar
cristy committed
216 217 218
  while (s > segment_stack)
  {
    register const IndexPacket
cristy's avatar
cristy committed
219
      *restrict indexes;
cristy's avatar
cristy committed
220 221

    register const PixelPacket
cristy's avatar
cristy committed
222
      *restrict p;
cristy's avatar
cristy committed
223

cristy's avatar
cristy committed
224
    register ssize_t
cristy's avatar
cristy committed
225 226 227
      x;

    register PixelPacket
cristy's avatar
cristy committed
228
      *restrict q;
cristy's avatar
cristy committed
229 230 231 232 233

    /*
      Pop segment off stack.
    */
    s--;
cristy's avatar
cristy committed
234 235 236 237
    x1=(ssize_t) s->x1;
    x2=(ssize_t) s->x2;
    offset=(ssize_t) s->y2;
    y=(ssize_t) s->y1+offset;
cristy's avatar
cristy committed
238 239 240
    /*
      Recolor neighboring pixels.
    */
cristy's avatar
cristy committed
241 242
    p=GetCacheViewVirtualPixels(image_view,0,y,(size_t) (x1+1),1,exception);
    q=GetCacheViewAuthenticPixels(floodplane_view,0,y,(size_t) (x1+1),1,
cristy's avatar
cristy committed
243 244 245
      exception);
    if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
      break;
cristy's avatar
cristy committed
246
    indexes=GetCacheViewVirtualIndexQueue(image_view);
cristy's avatar
cristy committed
247 248 249 250 251 252 253 254 255 256 257 258 259
    p+=x1;
    q+=x1;
    for (x=x1; x >= 0; x--)
    {
      if (q->opacity == (Quantum) TransparentOpacity)
        break;
      SetMagickPixelPacket(image,p,indexes+x,&pixel);
      if (IsMagickColorSimilar(&pixel,target) == invert)
        break;
      q->opacity=(Quantum) TransparentOpacity;
      p--;
      q--;
    }
cristy's avatar
cristy committed
260
    if (SyncCacheViewAuthenticPixels(floodplane_view,exception) == MagickFalse)
cristy's avatar
cristy committed
261 262 263 264 265 266 267 268 269 270 271 272 273
      break;
    skip=x >= x1 ? MagickTrue : MagickFalse;
    if (skip == MagickFalse)
      {
        start=x+1;
        if (start < x1)
          PushSegmentStack(y,start,x1-1,-offset);
        x=x1+1;
      }
    do
    {
      if (skip == MagickFalse)
        {
cristy's avatar
cristy committed
274
          if (x < (ssize_t) image->columns)
cristy's avatar
cristy committed
275
            {
cristy's avatar
cristy committed
276
              p=GetCacheViewVirtualPixels(image_view,x,y,image->columns-x,1,
cristy's avatar
cristy committed
277
                exception);
cristy's avatar
cristy committed
278 279
              q=GetCacheViewAuthenticPixels(floodplane_view,x,y,
                image->columns-x,1,exception);
cristy's avatar
cristy committed
280 281 282
              if ((p == (const PixelPacket *) NULL) ||
                  (q == (PixelPacket *) NULL))
                break;
cristy's avatar
cristy committed
283
              indexes=GetCacheViewVirtualIndexQueue(image_view);
cristy's avatar
cristy committed
284
              for ( ; x < (ssize_t) image->columns; x++)
cristy's avatar
cristy committed
285 286 287 288 289 290 291 292 293 294
              {
                if (q->opacity == (Quantum) TransparentOpacity)
                  break;
                SetMagickPixelPacket(image,p,indexes+x,&pixel);
                if (IsMagickColorSimilar(&pixel,target) == invert)
                  break;
                q->opacity=(Quantum) TransparentOpacity;
                p++;
                q++;
              }
cristy's avatar
cristy committed
295
              if (SyncCacheViewAuthenticPixels(floodplane_view,exception) == MagickFalse)
cristy's avatar
cristy committed
296 297 298 299 300 301 302 303 304 305
                break;
            }
          PushSegmentStack(y,start,x-1,offset);
          if (x > (x2+1))
            PushSegmentStack(y,x2+1,x-1,-offset);
        }
      skip=MagickFalse;
      x++;
      if (x <= x2)
        {
cristy's avatar
cristy committed
306 307 308
          p=GetCacheViewVirtualPixels(image_view,x,y,(size_t) (x2-x+1),1,
            exception);
          q=GetCacheViewAuthenticPixels(floodplane_view,x,y,(size_t) (x2-x+1),1,
cristy's avatar
cristy committed
309 310 311
            exception);
          if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
            break;
cristy's avatar
cristy committed
312
          indexes=GetCacheViewVirtualIndexQueue(image_view);
cristy's avatar
cristy committed
313 314 315 316 317 318 319 320 321 322 323 324 325 326
          for ( ; x <= x2; x++)
          {
            if (q->opacity == (Quantum) TransparentOpacity)
              break;
            SetMagickPixelPacket(image,p,indexes+x,&pixel);
            if (IsMagickColorSimilar(&pixel,target) != invert)
              break;
            p++;
            q++;
          }
        }
      start=x;
    } while (x <= x2);
  }
cristy's avatar
cristy committed
327
  for (y=0; y < (ssize_t) image->rows; y++)
cristy's avatar
cristy committed
328 329
  {
    register const PixelPacket
cristy's avatar
cristy committed
330
      *restrict p;
cristy's avatar
cristy committed
331 332

    register IndexPacket
cristy's avatar
cristy committed
333
      *restrict indexes;
cristy's avatar
cristy committed
334

cristy's avatar
cristy committed
335
    register ssize_t
cristy's avatar
cristy committed
336 337 338
      x;

    register PixelPacket
cristy's avatar
cristy committed
339
      *restrict q;
cristy's avatar
cristy committed
340 341 342 343

    /*
      Tile fill color onto floodplane.
    */
cristy's avatar
cristy committed
344 345 346
    p=GetCacheViewVirtualPixels(floodplane_view,0,y,image->columns,1,
      exception);
    q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristy's avatar
cristy committed
347 348
    if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
      break;
cristy's avatar
cristy committed
349
    indexes=GetCacheViewAuthenticIndexQueue(image_view);
cristy's avatar
cristy committed
350
    for (x=0; x < (ssize_t) image->columns; x++)
cristy's avatar
cristy committed
351
    {
cristy's avatar
cristy committed
352
      if (GetPixelOpacity(p) != OpaqueOpacity)
cristy's avatar
cristy committed
353 354 355 356
        {
          (void) GetFillColor(draw_info,x,y,&fill_color);
          SetMagickPixelPacket(image,&fill_color,(IndexPacket *) NULL,&fill);
          if (image->colorspace == CMYKColorspace)
cristy's avatar
cristy committed
357
            ConvertRGBToCMYK(&fill);
cristy's avatar
cristy committed
358
          if ((channel & RedChannel) != 0)
cristy's avatar
cristy committed
359
            SetPixelRed(q,ClampToQuantum(fill.red));
cristy's avatar
cristy committed
360
          if ((channel & GreenChannel) != 0)
cristy's avatar
cristy committed
361
            SetPixelGreen(q,ClampToQuantum(fill.green));
cristy's avatar
cristy committed
362
          if ((channel & BlueChannel) != 0)
cristy's avatar
cristy committed
363
            SetPixelBlue(q,ClampToQuantum(fill.blue));
cristy's avatar
cristy committed
364 365
          if (((channel & OpacityChannel) != 0) ||
              (draw_info->fill.opacity != OpaqueOpacity))
cristy's avatar
cristy committed
366
            SetPixelOpacity(q,ClampToQuantum(fill.opacity));
cristy's avatar
cristy committed
367 368
          if (((channel & IndexChannel) != 0) &&
              (image->colorspace == CMYKColorspace))
cristy's avatar
cristy committed
369
            SetPixelIndex(indexes+x,ClampToQuantum(fill.index));
cristy's avatar
cristy committed
370 371 372 373
        }
      p++;
      q++;
    }
cristy's avatar
cristy committed
374
    if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
cristy's avatar
cristy committed
375 376
      break;
  }
cristy's avatar
cristy committed
377 378
  floodplane_view=DestroyCacheView(floodplane_view);
  image_view=DestroyCacheView(image_view);
cristy's avatar
cristy committed
379
  segment_info=RelinquishVirtualMemory(segment_info);
cristy's avatar
cristy committed
380
  floodplane_image=DestroyImage(floodplane_image);
cristy's avatar
cristy committed
381
  return(y == (ssize_t) image->rows ? MagickTrue : MagickFalse);
cristy's avatar
cristy committed
382 383 384 385 386 387 388 389 390 391 392 393 394
}

/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                                                                             %
%                                                                             %
%                                                                             %
+     G r a d i e n t I m a g e                                               %
%                                                                             %
%                                                                             %
%                                                                             %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
cristy's avatar
cristy committed
395
%  GradientImage() applies a continuously smooth color transitions along a
cristy's avatar
cristy committed
396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421
%  vector from one color to another.
%
%  Note, the interface of this method will change in the future to support
%  more than one transistion.
%
%  The format of the GradientImage method is:
%
%      MagickBooleanType GradientImage(Image *image,const GradientType type,
%        const SpreadMethod method,const PixelPacket *start_color,
%        const PixelPacket *stop_color)
%
%  A description of each parameter follows:
%
%    o image: the image.
%
%    o type: the gradient type: linear or radial.
%
%    o spread: the gradient spread meathod: pad, reflect, or repeat.
%
%    o start_color: the start color.
%
%    o stop_color: the stop color.
%
% This provides a good example of making use of the DrawGradientImage
% function and the gradient structure in draw_info.
*/
cristy's avatar
cristy committed
422 423 424 425 426 427

static inline double MagickMax(const double x,const double y)
{
  return(x > y ? x : y);
}

cristy's avatar
cristy committed
428 429 430 431 432 433 434 435 436 437 438 439 440
MagickExport MagickBooleanType GradientImage(Image *image,
  const GradientType type,const SpreadMethod method,
  const PixelPacket *start_color,const PixelPacket *stop_color)
{
  DrawInfo
    *draw_info;

  GradientInfo
    *gradient;

  MagickBooleanType
    status;

cristy's avatar
cristy committed
441
  register ssize_t
cristy's avatar
cristy committed
442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476
    i;

  /*
    Set gradient start-stop end points.
  */
  assert(image != (const Image *) NULL);
  assert(image->signature == MagickSignature);
  if (image->debug != MagickFalse)
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
  assert(start_color != (const PixelPacket *) NULL);
  assert(stop_color != (const PixelPacket *) NULL);
  draw_info=AcquireDrawInfo();
  gradient=(&draw_info->gradient);
  gradient->type=type;
  gradient->bounding_box.width=image->columns;
  gradient->bounding_box.height=image->rows;
  gradient->gradient_vector.x2=(double) image->columns-1.0;
  gradient->gradient_vector.y2=(double) image->rows-1.0;
  if ((type == LinearGradient) && (gradient->gradient_vector.y2 != 0.0))
    gradient->gradient_vector.x2=0.0;
  gradient->center.x=(double) gradient->gradient_vector.x2/2.0;
  gradient->center.y=(double) gradient->gradient_vector.y2/2.0;
  gradient->radius=MagickMax(gradient->center.x,gradient->center.y);
  gradient->spread=method;
  /*
    Define the gradient to fill between the stops.
  */
  gradient->number_stops=2;
  gradient->stops=(StopInfo *) AcquireQuantumMemory(gradient->number_stops,
    sizeof(*gradient->stops));
  if (gradient->stops == (StopInfo *) NULL)
    ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
      image->filename);
  (void) ResetMagickMemory(gradient->stops,0,gradient->number_stops*
    sizeof(*gradient->stops));
cristy's avatar
cristy committed
477
  for (i=0; i < (ssize_t) gradient->number_stops; i++)
cristy's avatar
cristy committed
478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522
    GetMagickPixelPacket(image,&gradient->stops[i].color);
  SetMagickPixelPacket(image,start_color,(IndexPacket *) NULL,
    &gradient->stops[0].color);
  gradient->stops[0].offset=0.0;
  SetMagickPixelPacket(image,stop_color,(IndexPacket *) NULL,
    &gradient->stops[1].color);
  gradient->stops[1].offset=1.0;
  /*
    Draw a gradient on the image.
  */
  status=DrawGradientImage(image,draw_info);
  draw_info=DestroyDrawInfo(draw_info);
  return(status);
}

/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                                                                             %
%                                                                             %
%                                                                             %
%     O i l P a i n t I m a g e                                               %
%                                                                             %
%                                                                             %
%                                                                             %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
%  OilPaintImage() applies a special effect filter that simulates an oil
%  painting.  Each pixel is replaced by the most frequent color occurring
%  in a circular region defined by radius.
%
%  The format of the OilPaintImage method is:
%
%      Image *OilPaintImage(const Image *image,const double radius,
%        ExceptionInfo *exception)
%
%  A description of each parameter follows:
%
%    o image: the image.
%
%    o radius: the radius of the circular neighborhood.
%
%    o exception: return any errors or warnings in this structure.
%
*/

cristy's avatar
cristy committed
523
static size_t **DestroyHistogramThreadSet(size_t **histogram)
cristy's avatar
cristy committed
524
{
cristy's avatar
cristy committed
525
  register ssize_t
cristy's avatar
cristy committed
526 527
    i;

cristy's avatar
cristy committed
528
  assert(histogram != (size_t **) NULL);
cristy's avatar
cristy committed
529
  for (i=0; i < (ssize_t) GetMagickResourceLimit(ThreadResource); i++)
cristy's avatar
cristy committed
530 531
    if (histogram[i] != (size_t *) NULL)
      histogram[i]=(size_t *) RelinquishMagickMemory(histogram[i]);
cristy's avatar
cristy committed
532
  histogram=(size_t **) RelinquishMagickMemory(histogram);
cristy's avatar
cristy committed
533 534 535
  return(histogram);
}

cristy's avatar
cristy committed
536
static size_t **AcquireHistogramThreadSet(const size_t count)
cristy's avatar
cristy committed
537
{
cristy's avatar
cristy committed
538
  register ssize_t
cristy's avatar
cristy committed
539 540
    i;

cristy's avatar
cristy committed
541
  size_t
cristy's avatar
cristy committed
542 543 544
    **histogram,
    number_threads;

cristy's avatar
cristy committed
545
  number_threads=(size_t) GetMagickResourceLimit(ThreadResource);
cristy's avatar
cristy committed
546
  histogram=(size_t **) AcquireQuantumMemory(number_threads,
cristy's avatar
cristy committed
547
    sizeof(*histogram));
cristy's avatar
cristy committed
548 549
  if (histogram == (size_t **) NULL)
    return((size_t **) NULL);
cristy's avatar
cristy committed
550
  (void) ResetMagickMemory(histogram,0,number_threads*sizeof(*histogram));
cristy's avatar
cristy committed
551
  for (i=0; i < (ssize_t) number_threads; i++)
cristy's avatar
cristy committed
552
  {
cristy's avatar
cristy committed
553
    histogram[i]=(size_t *) AcquireQuantumMemory(count,
cristy's avatar
cristy committed
554
      sizeof(**histogram));
cristy's avatar
cristy committed
555
    if (histogram[i] == (size_t *) NULL)
cristy's avatar
cristy committed
556 557 558 559 560 561 562 563 564 565 566
      return(DestroyHistogramThreadSet(histogram));
  }
  return(histogram);
}

MagickExport Image *OilPaintImage(const Image *image,const double radius,
  ExceptionInfo *exception)
{
#define NumberPaintBins  256
#define OilPaintImageTag  "OilPaint/Image"

cristy's avatar
cristy committed
567 568 569 570
  CacheView
    *image_view,
    *paint_view;

cristy's avatar
cristy committed
571
  Image
cristy's avatar
cristy committed
572
    *linear_image,
cristy's avatar
cristy committed
573 574 575 576 577
    *paint_image;

  MagickBooleanType
    status;

cristy's avatar
cristy committed
578 579 580 581
  MagickOffsetType
    progress;

  size_t
cristy's avatar
cristy committed
582
    **restrict histograms,
cristy's avatar
cristy committed
583 584
    width;

cristy's avatar
cristy committed
585 586 587
  ssize_t
    y;

cristy's avatar
cristy committed
588 589 590 591 592 593 594 595 596 597
  /*
    Initialize painted image attributes.
  */
  assert(image != (const Image *) NULL);
  assert(image->signature == MagickSignature);
  if (image->debug != MagickFalse)
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
  assert(exception != (ExceptionInfo *) NULL);
  assert(exception->signature == MagickSignature);
  width=GetOptimalKernelWidth2D(radius,0.5);
cristy's avatar
cristy committed
598
  linear_image=CloneImage(image,0,0,MagickTrue,exception);
cristy's avatar
cristy committed
599
  paint_image=CloneImage(image,image->columns,image->rows,MagickTrue,exception);
cristy's avatar
cristy committed
600 601 602 603 604 605 606 607
  if ((linear_image == (Image *) NULL) || (paint_image == (Image *) NULL))
    {
      if (linear_image != (Image *) NULL)
        linear_image=DestroyImage(linear_image);
      if (paint_image != (Image *) NULL)
        linear_image=DestroyImage(paint_image);
      return((Image *) NULL);
    }
cristy's avatar
cristy committed
608 609 610
  if (SetImageStorageClass(paint_image,DirectClass) == MagickFalse)
    {
      InheritException(exception,&paint_image->exception);
cristy's avatar
cristy committed
611
      linear_image=DestroyImage(linear_image);
cristy's avatar
cristy committed
612 613 614 615
      paint_image=DestroyImage(paint_image);
      return((Image *) NULL);
    }
  histograms=AcquireHistogramThreadSet(NumberPaintBins);
cristy's avatar
cristy committed
616
  if (histograms == (size_t **) NULL)
cristy's avatar
cristy committed
617
    {
cristy's avatar
cristy committed
618
      linear_image=DestroyImage(linear_image);
cristy's avatar
cristy committed
619 620 621 622 623 624 625 626
      paint_image=DestroyImage(paint_image);
      ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
    }
  /*
    Oil paint image.
  */
  status=MagickTrue;
  progress=0;
cristy's avatar
cristy committed
627
  image_view=AcquireVirtualCacheView(linear_image,exception);
cristy's avatar
cristy committed
628
  paint_view=AcquireAuthenticCacheView(paint_image,exception);
cristy's avatar
cristy committed
629
#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy's avatar
cristy committed
630
  #pragma omp parallel for schedule(static,4) shared(progress,status) \
cristy's avatar
cristy committed
631
    magick_threads(linear_image,paint_image,linear_image->rows,1)
cristy's avatar
cristy committed
632
#endif
cristy's avatar
cristy committed
633
  for (y=0; y < (ssize_t) linear_image->rows; y++)
cristy's avatar
cristy committed
634 635
  {
    register const IndexPacket
cristy's avatar
cristy committed
636
      *restrict indexes;
cristy's avatar
cristy committed
637 638

    register const PixelPacket
cristy's avatar
cristy committed
639
      *restrict p;
cristy's avatar
cristy committed
640 641

    register IndexPacket
cristy's avatar
cristy committed
642
      *restrict paint_indexes;
cristy's avatar
cristy committed
643

cristy's avatar
cristy committed
644
    register ssize_t
cristy's avatar
cristy committed
645 646 647
      x;

    register PixelPacket
cristy's avatar
cristy committed
648
      *restrict q;
cristy's avatar
cristy committed
649

cristy's avatar
cristy committed
650
    register size_t
cristy's avatar
cristy committed
651 652 653 654
      *histogram;

    if (status == MagickFalse)
      continue;
cristy's avatar
cristy committed
655
    p=GetCacheViewVirtualPixels(image_view,-((ssize_t) width/2L),y-(ssize_t)
cristy's avatar
cristy committed
656
      (width/2L),linear_image->columns+width,width,exception);
cristy's avatar
cristy committed
657 658 659 660 661 662 663 664 665 666
    q=QueueCacheViewAuthenticPixels(paint_view,0,y,paint_image->columns,1,
      exception);
    if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
      {
        status=MagickFalse;
        continue;
      }
    indexes=GetCacheViewVirtualIndexQueue(image_view);
    paint_indexes=GetCacheViewAuthenticIndexQueue(paint_view);
    histogram=histograms[GetOpenMPThreadId()];
cristy's avatar
cristy committed
667
    for (x=0; x < (ssize_t) linear_image->columns; x++)
cristy's avatar
cristy committed
668
    {
cristy's avatar
cristy committed
669
      register ssize_t
cristy's avatar
cristy committed
670 671 672
        i,
        u;

cristy's avatar
cristy committed
673
      size_t
cristy's avatar
cristy committed
674 675
        count;

cristy's avatar
cristy committed
676 677 678 679 680
      ssize_t
        j,
        k,
        v;

cristy's avatar
cristy committed
681 682 683 684 685 686 687
      /*
        Assign most frequent color.
      */
      i=0;
      j=0;
      count=0;
      (void) ResetMagickMemory(histogram,0,NumberPaintBins*sizeof(*histogram));
cristy's avatar
cristy committed
688
      for (v=0; v < (ssize_t) width; v++)
cristy's avatar
cristy committed
689
      {
cristy's avatar
cristy committed
690
        for (u=0; u < (ssize_t) width; u++)
cristy's avatar
cristy committed
691
        {
cristy's avatar
cristy committed
692 693
          k=(ssize_t) ScaleQuantumToChar(ClampToQuantum(GetPixelIntensity(
            linear_image,p+u+i)));
cristy's avatar
cristy committed
694 695 696 697 698 699 700
          histogram[k]++;
          if (histogram[k] > count)
            {
              j=i+u;
              count=histogram[k];
            }
        }
cristy's avatar
cristy committed
701
        i+=(ssize_t) (linear_image->columns+width);
cristy's avatar
cristy committed
702 703
      }
      *q=(*(p+j));
cristy's avatar
cristy committed
704
      if (linear_image->colorspace == CMYKColorspace)
cristy's avatar
cristy committed
705
        SetPixelIndex(paint_indexes+x,GetPixelIndex(indexes+x+j));
cristy's avatar
cristy committed
706 707 708 709 710
      p++;
      q++;
    }
    if (SyncCacheViewAuthenticPixels(paint_view,exception) == MagickFalse)
      status=MagickFalse;
cristy's avatar
cristy committed
711
    if (image->progress_monitor != (MagickProgressMonitor) NULL)
cristy's avatar
cristy committed
712 713 714 715
      {
        MagickBooleanType
          proceed;

cristy's avatar
cristy committed
716
#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy's avatar
cristy committed
717
        #pragma omp critical (MagickCore_OilPaintImage)
cristy's avatar
cristy committed
718
#endif
cristy's avatar
cristy committed
719
        proceed=SetImageProgress(image,OilPaintImageTag,progress++,image->rows);
cristy's avatar
cristy committed
720 721 722 723 724 725 726
        if (proceed == MagickFalse)
          status=MagickFalse;
      }
  }
  paint_view=DestroyCacheView(paint_view);
  image_view=DestroyCacheView(image_view);
  histograms=DestroyHistogramThreadSet(histograms);
cristy's avatar
cristy committed
727
  linear_image=DestroyImage(linear_image);
cristy's avatar
cristy committed
728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779
  if (status == MagickFalse)
    paint_image=DestroyImage(paint_image);
  return(paint_image);
}

/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                                                                             %
%                                                                             %
%                                                                             %
%     O p a q u e P a i n t I m a g e                                         %
%                                                                             %
%                                                                             %
%                                                                             %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
%  OpaquePaintImage() changes any pixel that matches color with the color
%  defined by fill.
%
%  By default color must match a particular pixel color exactly.  However,
%  in many cases two colors may differ by a small amount.  Fuzz defines
%  how much tolerance is acceptable to consider two colors as the same.
%  For example, set fuzz to 10 and the color red at intensities of 100 and
%  102 respectively are now interpreted as the same color.
%
%  The format of the OpaquePaintImage method is:
%
%      MagickBooleanType OpaquePaintImage(Image *image,
%        const PixelPacket *target,const PixelPacket *fill,
%        const MagickBooleanType invert)
%      MagickBooleanType OpaquePaintImageChannel(Image *image,
%        const ChannelType channel,const PixelPacket *target,
%        const PixelPacket *fill,const MagickBooleanType invert)
%
%  A description of each parameter follows:
%
%    o image: the image.
%
%    o channel: the channel(s).
%
%    o target: the RGB value of the target color.
%
%    o fill: the replacement color.
%
%    o invert: paint any pixel that does not match the target color.
%
*/

MagickExport MagickBooleanType OpaquePaintImage(Image *image,
  const MagickPixelPacket *target,const MagickPixelPacket *fill,
  const MagickBooleanType invert)
{
cristy's avatar
cristy committed
780
  return(OpaquePaintImageChannel(image,CompositeChannels,target,fill,invert));
cristy's avatar
cristy committed
781 782 783 784 785 786 787 788
}

MagickExport MagickBooleanType OpaquePaintImageChannel(Image *image,
  const ChannelType channel,const MagickPixelPacket *target,
  const MagickPixelPacket *fill,const MagickBooleanType invert)
{
#define OpaquePaintImageTag  "Opaque/Image"

cristy's avatar
cristy committed
789 790 791
  CacheView
    *image_view;

cristy's avatar
cristy committed
792 793 794 795 796 797
  ExceptionInfo
    *exception;

  MagickBooleanType
    status;

cristy's avatar
cristy committed
798 799 800
  MagickOffsetType
    progress;

cristy's avatar
cristy committed
801 802 803
  MagickPixelPacket
    zero;

cristy's avatar
cristy committed
804 805 806
  ssize_t
    y;

cristy's avatar
cristy committed
807 808 809 810 811 812 813 814
  assert(image != (Image *) NULL);
  assert(image->signature == MagickSignature);
  assert(target != (MagickPixelPacket *) NULL);
  assert(fill != (MagickPixelPacket *) NULL);
  if (image->debug != MagickFalse)
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
  if (SetImageStorageClass(image,DirectClass) == MagickFalse)
    return(MagickFalse);
cristy's avatar
cristy committed
815 816
  if ((IsGrayColorspace(image->colorspace) != MagickFalse) &&
      (IsMagickGray(fill) == MagickFalse))
cristy's avatar
cristy committed
817
    (void) SetImageColorspace(image,sRGBColorspace);
cristy's avatar
cristy committed
818
  if ((fill->opacity != OpaqueOpacity) && (image->matte == MagickFalse))
cristy's avatar
cristy committed
819
    (void) SetImageAlphaChannel(image,OpaqueAlphaChannel);
cristy's avatar
cristy committed
820 821 822 823 824 825 826
  /*
    Make image color opaque.
  */
  status=MagickTrue;
  progress=0;
  exception=(&image->exception);
  GetMagickPixelPacket(image,&zero);
cristy's avatar
cristy committed
827
  image_view=AcquireAuthenticCacheView(image,exception);
cristy's avatar
cristy committed
828
#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy's avatar
cristy committed
829
  #pragma omp parallel for schedule(static,4) shared(progress,status) \
cristy's avatar
cristy committed
830
    magick_threads(image,image,image->rows,1)
cristy's avatar
cristy committed
831
#endif
cristy's avatar
cristy committed
832
  for (y=0; y < (ssize_t) image->rows; y++)
cristy's avatar
cristy committed
833 834 835 836 837
  {
    MagickPixelPacket
      pixel;

    register IndexPacket
cristy's avatar
cristy committed
838
      *restrict indexes;
cristy's avatar
cristy committed
839

cristy's avatar
cristy committed
840
    register ssize_t
cristy's avatar
cristy committed
841 842 843
      x;

    register PixelPacket
cristy's avatar
cristy committed
844
      *restrict q;
cristy's avatar
cristy committed
845 846 847 848 849 850 851 852 853 854 855

    if (status == MagickFalse)
      continue;
    q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
    if (q == (PixelPacket *) NULL)
      {
        status=MagickFalse;
        continue;
      }
    indexes=GetCacheViewAuthenticIndexQueue(image_view);
    pixel=zero;
cristy's avatar
cristy committed
856
    for (x=0; x < (ssize_t) image->columns; x++)
cristy's avatar
cristy committed
857 858 859 860 861
    {
      SetMagickPixelPacket(image,q,indexes+x,&pixel);
      if (IsMagickColorSimilar(&pixel,target) != invert)
        {
          if ((channel & RedChannel) != 0)
cristy's avatar
cristy committed
862
            SetPixelRed(q,ClampToQuantum(fill->red));
cristy's avatar
cristy committed
863
          if ((channel & GreenChannel) != 0)
cristy's avatar
cristy committed
864
            SetPixelGreen(q,ClampToQuantum(fill->green));
cristy's avatar
cristy committed
865
          if ((channel & BlueChannel) != 0)
cristy's avatar
cristy committed
866
            SetPixelBlue(q,ClampToQuantum(fill->blue));
cristy's avatar
cristy committed
867
          if ((channel & OpacityChannel) != 0)
cristy's avatar
cristy committed
868
            SetPixelOpacity(q,ClampToQuantum(fill->opacity));
cristy's avatar
cristy committed
869 870
          if (((channel & IndexChannel) != 0) &&
              (image->colorspace == CMYKColorspace))
cristy's avatar
cristy committed
871
            SetPixelIndex(indexes+x,ClampToQuantum(fill->index));
cristy's avatar
cristy committed
872 873 874 875 876 877 878 879 880 881
        }
      q++;
    }
    if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
      status=MagickFalse;
    if (image->progress_monitor != (MagickProgressMonitor) NULL)
      {
        MagickBooleanType
          proceed;

cristy's avatar
cristy committed
882
#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy's avatar
cristy committed
883
        #pragma omp critical (MagickCore_OpaquePaintImageChannel)
cristy's avatar
cristy committed
884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937
#endif
        proceed=SetImageProgress(image,OpaquePaintImageTag,progress++,
          image->rows);
        if (proceed == MagickFalse)
          status=MagickFalse;
      }
  }
  image_view=DestroyCacheView(image_view);
  return(status);
}

/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                                                                             %
%                                                                             %
%                                                                             %
%     T r a n s p a r e n t P a i n t I m a g e                               %
%                                                                             %
%                                                                             %
%                                                                             %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
%  TransparentPaintImage() changes the opacity value associated with any pixel
%  that matches color to the value defined by opacity.
%
%  By default color must match a particular pixel color exactly.  However,
%  in many cases two colors may differ by a small amount.  Fuzz defines
%  how much tolerance is acceptable to consider two colors as the same.
%  For example, set fuzz to 10 and the color red at intensities of 100 and
%  102 respectively are now interpreted as the same color.
%
%  The format of the TransparentPaintImage method is:
%
%      MagickBooleanType TransparentPaintImage(Image *image,
%        const MagickPixelPacket *target,const Quantum opacity,
%        const MagickBooleanType invert)
%
%  A description of each parameter follows:
%
%    o image: the image.
%
%    o target: the target color.
%
%    o opacity: the replacement opacity value.
%
%    o invert: paint any pixel that does not match the target color.
%
*/
MagickExport MagickBooleanType TransparentPaintImage(Image *image,
  const MagickPixelPacket *target,const Quantum opacity,
  const MagickBooleanType invert)
{
#define TransparentPaintImageTag  "Transparent/Image"

cristy's avatar
cristy committed
938 939 940
  CacheView
    *image_view;

cristy's avatar
cristy committed
941 942 943 944 945 946
  ExceptionInfo
    *exception;

  MagickBooleanType
    status;

cristy's avatar
cristy committed
947 948 949
  MagickOffsetType
    progress;

cristy's avatar
cristy committed
950 951 952
  MagickPixelPacket
    zero;

cristy's avatar
cristy committed
953 954 955
  ssize_t
    y;

cristy's avatar
cristy committed
956 957 958 959 960 961 962 963
  assert(image != (Image *) NULL);
  assert(image->signature == MagickSignature);
  assert(target != (MagickPixelPacket *) NULL);
  if (image->debug != MagickFalse)
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
  if (SetImageStorageClass(image,DirectClass) == MagickFalse)
    return(MagickFalse);
  if (image->matte == MagickFalse)
cristy's avatar
cristy committed
964
    (void) SetImageAlphaChannel(image,OpaqueAlphaChannel);
cristy's avatar
cristy committed
965 966 967 968 969 970 971
  /*
    Make image color transparent.
  */
  status=MagickTrue;
  progress=0;
  exception=(&image->exception);
  GetMagickPixelPacket(image,&zero);
cristy's avatar
cristy committed
972
  image_view=AcquireAuthenticCacheView(image,exception);
cristy's avatar
cristy committed
973
#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy's avatar
cristy committed
974
  #pragma omp parallel for schedule(static,4) shared(progress,status) \
cristy's avatar
cristy committed
975
    magick_threads(image,image,image->rows,1)
cristy's avatar
cristy committed
976
#endif
cristy's avatar
cristy committed
977
  for (y=0; y < (ssize_t) image->rows; y++)
cristy's avatar
cristy committed
978 979 980 981 982
  {
    MagickPixelPacket
      pixel;

    register IndexPacket
cristy's avatar
cristy committed
983
      *restrict indexes;
cristy's avatar
cristy committed
984

cristy's avatar
cristy committed
985
    register ssize_t
cristy's avatar
cristy committed
986 987 988
      x;

    register PixelPacket
cristy's avatar
cristy committed
989
      *restrict q;
cristy's avatar
cristy committed
990 991 992 993 994 995 996 997 998 999 1000

    if (status == MagickFalse)
      continue;
    q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
    if (q == (PixelPacket *) NULL)
      {
        status=MagickFalse;
        continue;
      }
    indexes=GetCacheViewAuthenticIndexQueue(image_view);
    pixel=zero;
cristy's avatar
cristy committed
1001
    for (x=0; x < (ssize_t) image->columns; x++)
cristy's avatar
cristy committed
1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014
    {
      SetMagickPixelPacket(image,q,indexes+x,&pixel);
      if (IsMagickColorSimilar(&pixel,target) != invert)
        q->opacity=opacity;
      q++;
    }
    if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
      status=MagickFalse;
    if (image->progress_monitor != (MagickProgressMonitor) NULL)
      {
        MagickBooleanType
          proceed;

cristy's avatar
cristy committed
1015
#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy's avatar
cristy committed
1016
        #pragma omp critical (MagickCore_TransparentPaintImage)
cristy's avatar
cristy committed
1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073
#endif
        proceed=SetImageProgress(image,TransparentPaintImageTag,progress++,
          image->rows);
        if (proceed == MagickFalse)
          status=MagickFalse;
      }
  }
  image_view=DestroyCacheView(image_view);
  return(status);
}

/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                                                                             %
%                                                                             %
%                                                                             %
%     T r a n s p a r e n t P a i n t I m a g e C h r o m a                   %
%                                                                             %
%                                                                             %
%                                                                             %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
%  TransparentPaintImageChroma() changes the opacity value associated with any
%  pixel that matches color to the value defined by opacity.
%
%  As there is one fuzz value for the all the channels, the
%  TransparentPaintImage() API is not suitable for the operations like chroma,
%  where the tolerance for similarity of two color component (RGB) can be
%  different, Thus we define this method take two target pixels (one
%  low and one hight) and all the pixels of an image which are lying between
%  these two pixels are made transparent.
%
%  The format of the TransparentPaintImage method is:
%
%      MagickBooleanType TransparentPaintImage(Image *image,
%        const MagickPixelPacket *low,const MagickPixelPacket *hight,
%        const Quantum opacity,const MagickBooleanType invert)
%
%  A description of each parameter follows:
%
%    o image: the image.
%
%    o low: the low target color.
%
%    o high: the high target color.
%
%    o opacity: the replacement opacity value.
%
%    o invert: paint any pixel that does not match the target color.
%
*/
MagickExport MagickBooleanType TransparentPaintImageChroma(Image *image,
  const MagickPixelPacket *low,const MagickPixelPacket *high,
  const Quantum opacity,const MagickBooleanType invert)
{
#define TransparentPaintImageTag  "Transparent/Image"

cristy's avatar
cristy committed
1074 1075 1076
  CacheView
    *image_view;

cristy's avatar
cristy committed
1077 1078 1079 1080 1081 1082
  ExceptionInfo
    *exception;

  MagickBooleanType
    status;

cristy's avatar
cristy committed
1083 1084 1085 1086 1087 1088
  MagickOffsetType
    progress;

  ssize_t
    y;

cristy's avatar
cristy committed
1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104
  assert(image != (Image *) NULL);
  assert(image->signature == MagickSignature);
  assert(high != (MagickPixelPacket *) NULL);
  assert(low != (MagickPixelPacket *) NULL);
  if (image->debug != MagickFalse)
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
  if (SetImageStorageClass(image,DirectClass) == MagickFalse)
    return(MagickFalse);
  if (image->matte == MagickFalse)
    (void) SetImageAlphaChannel(image,ResetAlphaChannel);
  /*
    Make image color transparent.
  */
  status=MagickTrue;
  progress=0;
  exception=(&image->exception);
cristy's avatar
cristy committed
1105
  image_view=AcquireAuthenticCacheView(image,exception);
cristy's avatar
cristy committed
1106
#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy's avatar
cristy committed
1107
  #pragma omp parallel for schedule(static,4) shared(progress,status) \
cristy's avatar
cristy committed
1108
    magick_threads(image,image,image->rows,1)
cristy's avatar
cristy committed
1109
#endif
cristy's avatar
cristy committed
1110
  for (y=0; y < (ssize_t) image->rows; y++)
cristy's avatar
cristy committed
1111 1112 1113 1114 1115 1116 1117 1118
  {
    MagickBooleanType
      match;

    MagickPixelPacket
      pixel;

    register IndexPacket
cristy's avatar
cristy committed
1119
      *restrict indexes;
cristy's avatar
cristy committed
1120

cristy's avatar
cristy committed
1121
    register ssize_t
cristy's avatar
cristy committed
1122 1123 1124
      x;

    register PixelPacket
cristy's avatar
cristy committed
1125
      *restrict q;
cristy's avatar
cristy committed
1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136

    if (status == MagickFalse)
      continue;
    q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
    if (q == (PixelPacket *) NULL)
      {
        status=MagickFalse;
        continue;
      }
    indexes=GetCacheViewAuthenticIndexQueue(image_view);
    GetMagickPixelPacket(image,&pixel);
cristy's avatar
cristy committed
1137
    for (x=0; x < (ssize_t) image->columns; x++)
cristy's avatar
cristy committed
1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154
    {
      SetMagickPixelPacket(image,q,indexes+x,&pixel);
      match=((pixel.red >= low->red) && (pixel.red <= high->red) &&
        (pixel.green >= low->green) && (pixel.green <= high->green) &&
        (pixel.blue  >= low->blue) && (pixel.blue <= high->blue)) ?
        MagickTrue : MagickFalse;
      if (match != invert)
        q->opacity=opacity;
      q++;
    }
    if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
      status=MagickFalse;
    if (image->progress_monitor != (MagickProgressMonitor) NULL)
      {
        MagickBooleanType
          proceed;

cristy's avatar
cristy committed
1155
#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy's avatar
cristy committed
1156
        #pragma omp critical (MagickCore_TransparentPaintImageChroma)
cristy's avatar
cristy committed
1157 1158 1159 1160 1161 1162 1163 1164 1165 1166
#endif
        proceed=SetImageProgress(image,TransparentPaintImageTag,progress++,
          image->rows);
        if (proceed == MagickFalse)
          status=MagickFalse;
      }
  }
  image_view=DestroyCacheView(image_view);
  return(status);
}