% other 'operators' that internally use morphology operations as part of
% their processing.
%
-% It is basically equivalent to as MorphologyImage() (see below) but
-% without any user controls. This allows internel programs to use this
-% function, to actually perform a specific task without possible interference
-% by any API user supplied settings.
+% It is basically equivalent to as MorphologyImage() (see below) but without
+% any user controls. This allows internel programs to use this method to
+% perform a specific task without possible interference by any API user
+% supplied settings.
%
% It is MorphologyImage() task to extract any such user controls, and
% pass them to this function for processing.
% o exception: return any errors or warnings in this structure.
%
*/
-
-/* Apply a Morphology Primative to an image using the given kernel.
-** Two pre-created images must be provided, and no image is created.
-** It returns the number of pixels that changed between the images
-** for result convergence determination.
-*/
static ssize_t MorphologyPrimitive(const Image *image,Image *morphology_image,
const MorphologyMethod method,const KernelInfo *kernel,const double bias,
ExceptionInfo *exception)
*image_view,
*morphology_view;
+ OffsetInfo
+ offset;
+
ssize_t
- y, offx, offy;
+ y;
size_t
- virt_width,
+ width,
changed;
MagickBooleanType
assert(kernel->signature == MagickSignature);
assert(exception != (ExceptionInfo *) NULL);
assert(exception->signature == MagickSignature);
-
status=MagickTrue;
changed=0;
progress=0;
-
image_view=AcquireVirtualCacheView(image,exception);
morphology_view=AcquireAuthenticCacheView(morphology_image,exception);
- virt_width=image->columns+kernel->width-1;
-
- /* Some methods (including convolve) needs use a reflected kernel.
- * Adjust 'origin' offsets to loop though kernel as a reflection.
- */
- offx = kernel->x;
- offy = kernel->y;
- switch(method) {
+ width=image->columns+kernel->width-1;
+ switch (method)
+ {
case ConvolveMorphology:
case DilateMorphology:
case DilateIntensityMorphology:
case IterativeDistanceMorphology:
- /* kernel needs to used with reflection about origin */
- offx = (ssize_t) kernel->width-offx-1;
- offy = (ssize_t) kernel->height-offy-1;
+ {
+ /*
+ Kernel needs to used with reflection about origin.
+ */
+ offset.x=(ssize_t) kernel->width-kernel->x-1;
+ offset.y=(ssize_t) kernel->height-kernel->y-1;
break;
+ }
case ErodeMorphology:
case ErodeIntensityMorphology:
case HitAndMissMorphology:
case ThinningMorphology:
case ThickenMorphology:
- /* kernel is used as is, without reflection */
+ {
+ offset.x=kernel->x;
+ offset.y=kernel->y;
break;
+ }
default:
+ {
assert("Not a Primitive Morphology Method" != (char *) NULL);
break;
+ }
}
-
- if (method == ConvolveMorphology && kernel->width == 1)
+ if ((method == ConvolveMorphology) && (kernel->width == 1))
{
register ssize_t
x;
/*
- Anthony Thyssen, 14 June 2010
-
- Special handling (for speed) of vertical (blur) kernels. This
- performs its handling in columns rather than in rows. This is
- only done for convolve as it is the only method that generates very
- large 1-D vertical kernels (such as a 'BlurKernel')
-
- Timing tests (on single CPU laptop). Using a vertical 1-d Blue with
- normal row-by-row (below):
- time convert logo: -morphology Convolve Blur:0x10+90 null: 0.807u
- Using this column method
- time convert logo: -morphology Convolve Blur:0x10+90 null: 0.620u
+ Special handling (for speed) of vertical (blur) kernels. This performs
+ its handling in columns rather than in rows. This is only done
+ for convolve as it is the only method that generates very large 1-D
+ vertical kernels (such as a 'BlurKernel')
*/
#if defined(MAGICKCORE_OPENMP_SUPPORT)
#pragma omp parallel for schedule(static,4) shared(progress,status) \
if (status == MagickFalse)
continue;
- p=GetCacheViewVirtualPixels(image_view,x,-offy,1,image->rows+
+ p=GetCacheViewVirtualPixels(image_view,x,-offset.y,1,image->rows+
kernel->height-1,exception);
q=GetCacheViewAuthenticPixels(morphology_view,x,0,1,
morphology_image->rows,exception);
status=MagickFalse;
continue;
}
- center=(ssize_t) GetPixelChannels(image)*offy;
+ center=(ssize_t) GetPixelChannels(image)*offset.y;
for (y=0; y < (ssize_t) image->rows; y++)
{
register ssize_t
SetPixelChannel(morphology_image,channel,p[center+i],q);
continue;
}
- k=(&kernel->values[kernel->height-1]);
+ k=(&kernel->values[kernel->width*kernel->height-1]);
pixels=p;
pixel=bias;
gamma=0.0;
}
p+=GetPixelChannels(image);
q+=GetPixelChannels(morphology_image);
- } /* y */
+ }
if (SyncCacheViewAuthenticPixels(morphology_view,exception) == MagickFalse)
status=MagickFalse;
if (image->progress_monitor != (MagickProgressMonitor) NULL)
#if defined(MAGICKCORE_OPENMP_SUPPORT)
#pragma omp critical (MagickCore_MorphologyImage)
#endif
- proceed=SetImageProgress(image,MorphologyTag,progress++,image->rows);
+ proceed=SetImageProgress(image,MorphologyTag,progress++,
+ image->rows);
if (proceed == MagickFalse)
status=MagickFalse;
}
register ssize_t
x;
- size_t
- r;
+ ssize_t
+ center;
if (status == MagickFalse)
continue;
- p=GetCacheViewVirtualPixels(image_view,-offx,y-offy,virt_width,
+ p=GetCacheViewVirtualPixels(image_view,-offset.x,y-offset.y,width,
kernel->height,exception);
q=GetCacheViewAuthenticPixels(morphology_view,0,y,morphology_image->columns,
1,exception);
status=MagickFalse;
continue;
}
- /* offset to origin in 'p'. while 'q' points to it directly */
- r = GetPixelChannels(image)*virt_width*offy + GetPixelChannels(image)*offx;
-
+ center=(ssize_t) (GetPixelChannels(image)*width*offset.y+
+ GetPixelChannels(image)*offset.x);
for (x=0; x < (ssize_t) image->columns; x++)
{
- PixelInfo
- result,
- min,
- max;
+ register ssize_t
+ i;
- register const MagickRealType
- *restrict k;
+ for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
+ {
+ double
+ alpha,
+ gamma,
+ maximum,
+ minimum,
+ pixel;
- register const Quantum
- *restrict k_pixels;
+ PixelChannel
+ channel;
- register ssize_t
- u;
+ PixelTrait
+ morphology_traits,
+ traits;
- ssize_t
- v;
+ register const MagickRealType
+ *restrict k;
- /* Copy input image to the output image for unused channels
- * This removes need for 'cloning' a new image every iteration
- */
- SetPixelRed(morphology_image,GetPixelRed(image,p+r),q);
- SetPixelGreen(morphology_image,GetPixelGreen(image,p+r),q);
- SetPixelBlue(morphology_image,GetPixelBlue(image,p+r),q);
- if (image->colorspace == CMYKColorspace)
- SetPixelBlack(morphology_image,GetPixelBlack(image,p+r),q);
-
- /* Defaults */
- min.red =
- min.green =
- min.blue =
- min.alpha =
- min.black = (double) QuantumRange;
- max.red =
- max.green =
- max.blue =
- max.alpha =
- max.black = (double) 0;
- /* default result is the original pixel value */
- result.red = (double) GetPixelRed(image,p+r);
- result.green = (double) GetPixelGreen(image,p+r);
- result.blue = (double) GetPixelBlue(image,p+r);
- result.black = 0.0;
- if (image->colorspace == CMYKColorspace)
- result.black = (double) GetPixelBlack(image,p+r);
- result.alpha=(double) GetPixelAlpha(image,p+r);
-
- switch (method) {
- case ConvolveMorphology:
- /* Set the bias of the weighted average output */
- result.red =
- result.green =
- result.blue =
- result.alpha =
- result.black = bias;
- break;
- case DilateIntensityMorphology:
- case ErodeIntensityMorphology:
- /* use a boolean flag indicating when first match found */
- result.red = 0.0; /* result is not used otherwise */
- break;
- default:
- break;
- }
+ register const Quantum
+ *restrict pixels;
+
+ register ssize_t
+ u;
+
+ ssize_t
+ v;
+
+ channel=GetPixelChannelChannel(image,i);
+ traits=GetPixelChannelTraits(image,channel);
+ morphology_traits=GetPixelChannelTraits(morphology_image,channel);
+ if ((traits == UndefinedPixelTrait) ||
+ (morphology_traits == UndefinedPixelTrait))
+ continue;
+ if (((morphology_traits & CopyPixelTrait) != 0) ||
+ (GetPixelMask(image,p) != 0))
+ {
+ SetPixelChannel(morphology_image,channel,p[center+i],q);
+ continue;
+ }
+ pixels=p;
+ maximum=0.0;
+ minimum=(double) QuantumRange;
+ switch (method)
+ {
+ case ConvolveMorphology: pixel=bias; break;
+ case HitAndMissMorphology: pixel=(double) QuantumRange; break;
+ case ThinningMorphology: pixel=(double) QuantumRange; break;
+ case ThickenMorphology: pixel=(double) QuantumRange; break;
+ case ErodeMorphology: pixel=(double) QuantumRange; break;
+ case DilateMorphology: pixel=0.0; break;
+ case ErodeIntensityMorphology:
+ case DilateIntensityMorphology:
+ case IterativeDistanceMorphology:
+ {
+ pixel=(double) p[center+i];
+ break;
+ }
+ default: pixel=0; break;
+ }
+ gamma=0.0;
+ switch (method)
+ {
+ case ConvolveMorphology:
+ {
+ /*
+ Weighted Average of pixels using reflected kernel
+
+ For correct working of this operation for asymetrical
+ kernels, the kernel needs to be applied in its reflected form.
+ That is its values needs to be reversed.
- switch ( method ) {
- case ConvolveMorphology:
- /* Weighted Average of pixels using reflected kernel
- **
- ** NOTE for correct working of this operation for asymetrical
- ** kernels, the kernel needs to be applied in its reflected form.
- ** That is its values needs to be reversed.
- **
- ** Correlation is actually the same as this but without reflecting
- ** the kernel, and thus 'lower-level' that Convolution. However
- ** as Convolution is the more common method used, and it does not
- ** really cost us much in terms of processing to use a reflected
- ** kernel, so it is Convolution that is implemented.
- **
- ** Correlation will have its kernel reflected before calling
- ** this function to do a Convolve.
- **
- ** For more details of Correlation vs Convolution see
- ** http://www.cs.umd.edu/~djacobs/CMSC426/Convolution.pdf
+ Correlation is actually the same as this but without reflecting
+ the kernel, and thus 'lower-level' that Convolution. However
+ as Convolution is the more common method used, and it does not
+ really cost us much in terms of processing to use a reflected
+ kernel, so it is Convolution that is implemented.
+
+ Correlation will have its kernel reflected before calling
+ this function to do a Convolve.
+
+ For more details of Correlation vs Convolution see
+ http://www.cs.umd.edu/~djacobs/CMSC426/Convolution.pdf
*/
- k = &kernel->values[ kernel->width*kernel->height-1 ];
- k_pixels = p;
- if ( (image->channel_mask != DefaultChannels) ||
- (image->alpha_trait != BlendPixelTrait) )
- { /* No 'Sync' involved.
- ** Convolution is simple greyscale channel operation
+ k=(&kernel->values[kernel->width*kernel->height-1]);
+ if ((morphology_traits & BlendPixelTrait) == 0)
+ {
+ /*
+ No alpha blending.
*/
- for (v=0; v < (ssize_t) kernel->height; v++) {
- for (u=0; u < (ssize_t) kernel->width; u++, k--) {
- if ( IsNaN(*k) ) continue;
- result.red += (*k)*
- GetPixelRed(image,k_pixels+u*GetPixelChannels(image));
- result.green += (*k)*
- GetPixelGreen(image,k_pixels+u*GetPixelChannels(image));
- result.blue += (*k)*
- GetPixelBlue(image,k_pixels+u*GetPixelChannels(image));
- if (image->colorspace == CMYKColorspace)
- result.black += (*k)*
- GetPixelBlack(image,k_pixels+u*GetPixelChannels(image));
- result.alpha += (*k)*
- GetPixelAlpha(image,k_pixels+u*GetPixelChannels(image));
+ for (v=0; v < (ssize_t) kernel->height; v++)
+ {
+ for (u=0; u < (ssize_t) kernel->width; u++)
+ {
+ if (IsNaN(*k) == MagickFalse)
+ {
+ pixel+=(*k)*pixels[i];
+ gamma+=(*k);
+ }
+ k--;
+ pixels+=GetPixelChannels(image);
}
- k_pixels += virt_width*GetPixelChannels(image);
+ pixels+=image->columns*GetPixelChannels(image);
}
- if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
- SetPixelRed(morphology_image,ClampToQuantum(result.red),
- q);
- if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
- SetPixelGreen(morphology_image,ClampToQuantum(result.green),
- q);
- if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
- SetPixelBlue(morphology_image,ClampToQuantum(result.blue),
- q);
- if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
- (image->colorspace == CMYKColorspace))
- SetPixelBlack(morphology_image,ClampToQuantum(result.black),
- q);
- if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
- (image->alpha_trait == BlendPixelTrait))
- SetPixelAlpha(morphology_image,ClampToQuantum(result.alpha),
- q);
+ gamma=PerceptibleReciprocal(gamma);
+ pixel*=gamma;
+ continue;
}
- else
- { /* Channel 'Sync' Flag, and Alpha Channel enabled.
- ** Weight the color channels with Alpha Channel so that
- ** transparent pixels are not part of the results.
- */
- double
- gamma; /* divisor, sum of color alpha weighting */
-
- MagickRealType
- alpha; /* alpha weighting for colors : alpha */
-
- size_t
- count; /* alpha valus collected, number kernel values */
-
- count=0;
- gamma=0.0;
- for (v=0; v < (ssize_t) kernel->height; v++) {
- for (u=0; u < (ssize_t) kernel->width; u++, k--) {
- if ( IsNaN(*k) ) continue;
- alpha=QuantumScale*GetPixelAlpha(image,
- k_pixels+u*GetPixelChannels(image));
- gamma += alpha; /* normalize alpha weights only */
- count++; /* number of alpha values collected */
- alpha=alpha*(*k); /* include kernel weighting now */
- result.red += alpha*
- GetPixelRed(image,k_pixels+u*GetPixelChannels(image));
- result.green += alpha*
- GetPixelGreen(image,k_pixels+u*GetPixelChannels(image));
- result.blue += alpha*
- GetPixelBlue(image,k_pixels+u*GetPixelChannels(image));
- if (image->colorspace == CMYKColorspace)
- result.black += alpha*
- GetPixelBlack(image,k_pixels+u*GetPixelChannels(image));
- result.alpha += (*k)*
- GetPixelAlpha(image,k_pixels+u*GetPixelChannels(image));
+ /*
+ Alpha blending.
+ */
+ for (v=0; v < (ssize_t) kernel->width; v++)
+ {
+ for (u=0; u < (ssize_t) kernel->width; u++)
+ {
+ if (IsNaN(*k) == MagickFalse)
+ {
+ alpha=(double) (QuantumScale*GetPixelAlpha(image,pixels));
+ pixel+=(*k)*alpha*pixels[i];
+ gamma+=(*k)*alpha;
}
- k_pixels += virt_width*GetPixelChannels(image);
- }
- /* Sync'ed channels, all channels are modified */
- gamma=(double)count/(fabs((double) gamma) < MagickEpsilon ? MagickEpsilon : gamma);
- SetPixelRed(morphology_image,
- ClampToQuantum(gamma*result.red),q);
- SetPixelGreen(morphology_image,
- ClampToQuantum(gamma*result.green),q);
- SetPixelBlue(morphology_image,
- ClampToQuantum(gamma*result.blue),q);
- if (image->colorspace == CMYKColorspace)
- SetPixelBlack(morphology_image,
- ClampToQuantum(gamma*result.black),q);
- SetPixelAlpha(morphology_image,ClampToQuantum(result.alpha),q);
+ k--;
+ pixels+=GetPixelChannels(image);
}
+ pixels+=image->columns*GetPixelChannels(image);
+ }
+ gamma=PerceptibleReciprocal(gamma);
+ pixel*=gamma;
break;
+ }
+ case ErodeMorphology:
+ {
+ /*
+ Minimum value within kernel neighbourhood.
- case ErodeMorphology:
- /* Minimum Value within kernel neighbourhood
- **
- ** NOTE that the kernel is not reflected for this operation!
- **
- ** NOTE: in normal Greyscale Morphology, the kernel value should
- ** be added to the real value, this is currently not done, due to
- ** the nature of the boolean kernels being used.
+ The kernel is not reflected for this operation. In normal
+ Greyscale Morphology, the kernel value should be added
+ to the real value, this is currently not done, due to the
+ nature of the boolean kernels being used.
*/
- k = kernel->values;
- k_pixels = p;
- for (v=0; v < (ssize_t) kernel->height; v++) {
- for (u=0; u < (ssize_t) kernel->width; u++, k++) {
- if ( IsNaN(*k) || (*k) < 0.5 ) continue;
- Minimize(min.red, (double)
- GetPixelRed(image,k_pixels+u*GetPixelChannels(image)));
- Minimize(min.green, (double)
- GetPixelGreen(image,k_pixels+u*GetPixelChannels(image)));
- Minimize(min.blue, (double)
- GetPixelBlue(image,k_pixels+u*GetPixelChannels(image)));
- Minimize(min.alpha, (double)
- GetPixelAlpha(image,k_pixels+u*GetPixelChannels(image)));
- if (image->colorspace == CMYKColorspace)
- Minimize(min.black, (double)
- GetPixelBlack(image,k_pixels+u*GetPixelChannels(image)));
+ k=kernel->values;
+ for (v=0; v < (ssize_t) kernel->height; v++)
+ {
+ for (u=0; u < (ssize_t) kernel->width; u++)
+ {
+ if ((IsNaN(*k) == MagickFalse) && (*k >= 0.5))
+ {
+ if ((double) pixels[i] < pixel)
+ pixel=(double) pixels[i];
+ }
+ k++;
+ pixels+=GetPixelChannels(image);
}
- k_pixels += virt_width*GetPixelChannels(image);
+ pixels+=image->columns*GetPixelChannels(image);
}
break;
+ }
+ case DilateMorphology:
+ {
+ /*
+ Maximum value within kernel neighbourhood.
+
+ For correct working of this operation for asymetrical kernels,
+ the kernel needs to be applied in its reflected form. That is
+ its values needs to be reversed.
- case DilateMorphology:
- /* Maximum Value within kernel neighbourhood
- **
- ** NOTE for correct working of this operation for asymetrical
- ** kernels, the kernel needs to be applied in its reflected form.
- ** That is its values needs to be reversed.
- **
- ** NOTE: in normal Greyscale Morphology, the kernel value should
- ** be added to the real value, this is currently not done, due to
- ** the nature of the boolean kernels being used.
- **
+ In normal Greyscale Morphology, the kernel value should be
+ added to the real value, this is currently not done, due to the
+ nature of the boolean kernels being used.
*/
- k = &kernel->values[ kernel->width*kernel->height-1 ];
- k_pixels = p;
- for (v=0; v < (ssize_t) kernel->height; v++) {
- for (u=0; u < (ssize_t) kernel->width; u++, k--) {
- if ( IsNaN(*k) || (*k) < 0.5 ) continue;
- Maximize(max.red, (double)
- GetPixelRed(image,k_pixels+u*GetPixelChannels(image)));
- Maximize(max.green, (double)
- GetPixelGreen(image,k_pixels+u*GetPixelChannels(image)));
- Maximize(max.blue, (double)
- GetPixelBlue(image,k_pixels+u*GetPixelChannels(image)));
- Maximize(max.alpha, (double)
- GetPixelAlpha(image,k_pixels+u*GetPixelChannels(image)));
- if (image->colorspace == CMYKColorspace)
- Maximize(max.black, (double)
- GetPixelBlack(image,k_pixels+u*GetPixelChannels(image)));
+ k=(&kernel->values[kernel->width*kernel->height-1]);
+ for (v=0; v < (ssize_t) kernel->height; v++)
+ {
+ for (u=0; u < (ssize_t) kernel->width; u++)
+ {
+ if ((IsNaN(*k) == MagickFalse) && (*k > 0.5))
+ {
+ if ((double) pixels[i] > pixel)
+ pixel=(double) pixels[i];
+ }
+ k--;
+ pixels+=GetPixelChannels(image);
}
- k_pixels += virt_width*GetPixelChannels(image);
+ pixels+=image->columns*GetPixelChannels(image);
}
break;
+ }
+ case HitAndMissMorphology:
+ case ThinningMorphology:
+ case ThickenMorphology:
+ {
+ /*
+ Minimum of foreground pixel minus maxumum of background pixels.
+
+ The kernel is not reflected for this operation, and consists
+ of both foreground and background pixel neighbourhoods, 0.0 for
+ background, and 1.0 for foreground with either Nan or 0.5 values
+ for don't care.
- case HitAndMissMorphology:
- case ThinningMorphology:
- case ThickenMorphology:
- /* Minimum of Foreground Pixel minus Maxumum of Background Pixels
- **
- ** NOTE that the kernel is not reflected for this operation,
- ** and consists of both foreground and background pixel
- ** neighbourhoods, 0.0 for background, and 1.0 for foreground
- ** with either Nan or 0.5 values for don't care.
- **
- ** Note that this will never produce a meaningless negative
- ** result. Such results can cause Thinning/Thicken to not work
- ** correctly when used against a greyscale image.
+ This never produces a meaningless negative result. Such results
+ cause Thinning/Thicken to not work correctly when used against a
+ greyscale image.
*/
- k = kernel->values;
- k_pixels = p;
- for (v=0; v < (ssize_t) kernel->height; v++) {
- for (u=0; u < (ssize_t) kernel->width; u++, k++) {
- if ( IsNaN(*k) ) continue;
- if ( (*k) > 0.7 )
- { /* minimim of foreground pixels */
- Minimize(min.red, (double)
- GetPixelRed(image,k_pixels+u*GetPixelChannels(image)));
- Minimize(min.green, (double)
- GetPixelGreen(image,k_pixels+u*GetPixelChannels(image)));
- Minimize(min.blue, (double)
- GetPixelBlue(image,k_pixels+u*GetPixelChannels(image)));
- Minimize(min.alpha,(double)
- GetPixelAlpha(image,k_pixels+u*GetPixelChannels(image)));
- if ( image->colorspace == CMYKColorspace)
- Minimize(min.black,(double)
- GetPixelBlack(image,k_pixels+u*GetPixelChannels(image)));
- }
- else if ( (*k) < 0.3 )
- { /* maximum of background pixels */
- Maximize(max.red, (double)
- GetPixelRed(image,k_pixels+u*GetPixelChannels(image)));
- Maximize(max.green, (double)
- GetPixelGreen(image,k_pixels+u*GetPixelChannels(image)));
- Maximize(max.blue, (double)
- GetPixelBlue(image,k_pixels+u*GetPixelChannels(image)));
- Maximize(max.alpha,(double)
- GetPixelAlpha(image,k_pixels+u*GetPixelChannels(image)));
- if (image->colorspace == CMYKColorspace)
- Maximize(max.black, (double)
- GetPixelBlack(image,k_pixels+u*GetPixelChannels(image)));
- }
+ k=kernel->values;
+ for (v=0; v < (ssize_t) kernel->height; v++)
+ {
+ for (u=0; u < (ssize_t) kernel->width; u++)
+ {
+ if (IsNaN(*k) == MagickFalse)
+ {
+ if (*k > 0.7)
+ {
+ if ((double) pixels[i] < pixel)
+ pixel=(double) pixels[i];
+ }
+ else
+ if (*k < 0.3)
+ {
+ if ((double) pixels[i] > maximum)
+ maximum=(double) pixels[i];
+ }
+ }
+ k++;
+ pixels+=GetPixelChannels(image);
}
- k_pixels += virt_width*GetPixelChannels(image);
+ pixels+=image->columns*GetPixelChannels(image);
}
- /* Pattern Match if difference is positive */
- min.red -= max.red; Maximize( min.red, 0.0 );
- min.green -= max.green; Maximize( min.green, 0.0 );
- min.blue -= max.blue; Maximize( min.blue, 0.0 );
- min.black -= max.black; Maximize( min.black, 0.0 );
- min.alpha -= max.alpha; Maximize( min.alpha, 0.0 );
+ pixel-=maximum;
+ if (pixel < 0.0)
+ pixel=0.0;
+ if (method == ThinningMorphology)
+ pixel=(double) p[center+i]-pixel;
+ else
+ if (method == ThickenMorphology)
+ pixel+=(double) p[center+i]+pixel;
break;
-
- case ErodeIntensityMorphology:
- /* Select Pixel with Minimum Intensity within kernel neighbourhood
- **
- ** WARNING: the intensity test fails for CMYK and does not
- ** take into account the moderating effect of the alpha channel
- ** on the intensity.
- **
- ** NOTE that the kernel is not reflected for this operation!
+ }
+ case ErodeIntensityMorphology:
+ {
+ /*
+ Select pixel with minimum intensity within kernel neighbourhood.
+
+ The kernel is not reflected for this operation.
*/
- k = kernel->values;
- k_pixels = p;
- for (v=0; v < (ssize_t) kernel->height; v++) {
- for (u=0; u < (ssize_t) kernel->width; u++, k++) {
- if ( IsNaN(*k) || (*k) < 0.5 ) continue;
- if ( result.red == 0.0 ||
- GetPixelIntensity(image,k_pixels+u*GetPixelChannels(image)) < GetPixelIntensity(morphology_image,q) ) {
- /* copy the whole pixel - no channel selection */
- SetPixelRed(morphology_image,GetPixelRed(image,
- k_pixels+u*GetPixelChannels(image)),q);
- SetPixelGreen(morphology_image,GetPixelGreen(image,
- k_pixels+u*GetPixelChannels(image)),q);
- SetPixelBlue(morphology_image,GetPixelBlue(image,
- k_pixels+u*GetPixelChannels(image)),q);
- SetPixelAlpha(morphology_image,GetPixelAlpha(image,
- k_pixels+u*GetPixelChannels(image)),q);
- if ( result.red > 0.0 ) changed++;
- result.red = 1.0;
- }
+ k=kernel->values;
+ for (v=0; v < (ssize_t) kernel->height; v++)
+ {
+ for (u=0; u < (ssize_t) kernel->width; u++)
+ {
+ if ((IsNaN(*k) == MagickFalse) && (*k >= 0.5))
+ {
+ if (GetPixelIntensity(image,pixels) < minimum)
+ {
+ pixel=(double) pixels[i];
+ minimum=GetPixelIntensity(image,pixels);
+ }
+ }
+ k++;
+ pixels+=GetPixelChannels(image);
}
- k_pixels += virt_width*GetPixelChannels(image);
+ pixels+=image->columns*GetPixelChannels(image);
}
break;
-
- case DilateIntensityMorphology:
- /* Select Pixel with Maximum Intensity within kernel neighbourhood
- **
- ** WARNING: the intensity test fails for CMYK and does not
- ** take into account the moderating effect of the alpha channel
- ** on the intensity (yet).
- **
- ** NOTE for correct working of this operation for asymetrical
- ** kernels, the kernel needs to be applied in its reflected form.
- ** That is its values needs to be reversed.
+ }
+ case DilateIntensityMorphology:
+ {
+ /*
+ Select pixel with maximum intensity within kernel neighbourhood.
+
+ The kernel is not reflected for this operation.
*/
- k = &kernel->values[ kernel->width*kernel->height-1 ];
- k_pixels = p;
- for (v=0; v < (ssize_t) kernel->height; v++) {
- for (u=0; u < (ssize_t) kernel->width; u++, k--) {
- if ( IsNaN(*k) || (*k) < 0.5 ) continue; /* boolean kernel */
- if ( result.red == 0.0 ||
- GetPixelIntensity(image,k_pixels+u*GetPixelChannels(image)) > GetPixelIntensity(morphology_image,q) ) {
- /* copy the whole pixel - no channel selection */
- SetPixelRed(morphology_image,GetPixelRed(image,
- k_pixels+u*GetPixelChannels(image)),q);
- SetPixelGreen(morphology_image,GetPixelGreen(image,
- k_pixels+u*GetPixelChannels(image)),q);
- SetPixelBlue(morphology_image,GetPixelBlue(image,
- k_pixels+u*GetPixelChannels(image)),q);
- SetPixelAlpha(morphology_image,GetPixelAlpha(image,
- k_pixels+u*GetPixelChannels(image)),q);
- if ( result.red > 0.0 ) changed++;
- result.red = 1.0;
- }
+ k=(&kernel->values[kernel->width*kernel->height-1]);
+ for (v=0; v < (ssize_t) kernel->height; v++)
+ {
+ for (u=0; u < (ssize_t) kernel->width; u++)
+ {
+ if ((IsNaN(*k) == MagickFalse) && (*k >= 0.5))
+ {
+ if (GetPixelIntensity(image,pixels) > maximum)
+ {
+ pixel=(double) pixels[i];
+ maximum=GetPixelIntensity(image,pixels);
+ }
+ }
+ k--;
+ pixels+=GetPixelChannels(image);
}
- k_pixels += virt_width*GetPixelChannels(image);
+ pixels+=image->columns*GetPixelChannels(image);
}
break;
-
- case IterativeDistanceMorphology:
- /* Work out an iterative distance from black edge of a white image
- ** shape. Essentually white values are decreased to the smallest
- ** 'distance from edge' it can find.
- **
- ** It works by adding kernel values to the neighbourhood, and and
- ** select the minimum value found. The kernel is rotated before
- ** use, so kernel distances match resulting distances, when a user
- ** provided asymmetric kernel is applied.
- **
- **
- ** This code is almost identical to True GrayScale Morphology But
- ** not quite.
- **
- ** GreyDilate Kernel values added, maximum value found Kernel is
- ** rotated before use.
- **
- ** GrayErode: Kernel values subtracted and minimum value found No
- ** kernel rotation used.
- **
- ** Note the the Iterative Distance method is essentially a
- ** GrayErode, but with negative kernel values, and kernel
- ** rotation applied.
+ }
+ case IterativeDistanceMorphology:
+ {
+ /*
+ Compute th iterative distance from black edge of a white image
+ shape. Essentually white values are decreased to the smallest
+ 'distance from edge' it can find.
+
+ It works by adding kernel values to the neighbourhood, and and
+ select the minimum value found. The kernel is rotated before
+ use, so kernel distances match resulting distances, when a user
+ provided asymmetric kernel is applied.
+
+ This code is nearly identical to True GrayScale Morphology but
+ not quite.
+
+ GreyDilate Kernel values added, maximum value found Kernel is
+ rotated before use.
+
+ GrayErode: Kernel values subtracted and minimum value found No
+ kernel rotation used.
+
+ Note the the Iterative Distance method is essentially a
+ GrayErode, but with negative kernel values, and kernel rotation
+ applied.
*/
- k = &kernel->values[ kernel->width*kernel->height-1 ];
- k_pixels = p;
- for (v=0; v < (ssize_t) kernel->height; v++) {
- for (u=0; u < (ssize_t) kernel->width; u++, k--) {
- if ( IsNaN(*k) ) continue;
- Minimize(result.red, (*k)+(double)
- GetPixelRed(image,k_pixels+u*GetPixelChannels(image)));
- Minimize(result.green, (*k)+(double)
- GetPixelGreen(image,k_pixels+u*GetPixelChannels(image)));
- Minimize(result.blue, (*k)+(double)
- GetPixelBlue(image,k_pixels+u*GetPixelChannels(image)));
- Minimize(result.alpha, (*k)+(double)
- GetPixelAlpha(image,k_pixels+u*GetPixelChannels(image)));
- if ( image->colorspace == CMYKColorspace)
- Maximize(result.black, (*k)+(double)
- GetPixelBlack(image,k_pixels+u*GetPixelChannels(image)));
+ k=(&kernel->values[kernel->width*kernel->height-1]);
+ for (v=0; v < (ssize_t) kernel->height; v++)
+ {
+ for (u=0; u < (ssize_t) kernel->width; u++)
+ {
+ if (IsNaN(*k) == MagickFalse)
+ {
+ if ((pixels[i]+(*k)) < pixel)
+ pixel=(double) pixels[i]+(*k);
+ }
+ k--;
+ pixels+=GetPixelChannels(image);
}
- k_pixels += virt_width*GetPixelChannels(image);
+ pixels+=image->columns*GetPixelChannels(image);
}
break;
-
- case UndefinedMorphology:
- default:
- break; /* Do nothing */
- }
- /* Final mathematics of results (combine with original image?)
- **
- ** NOTE: Difference Morphology operators Edge* and *Hat could also
- ** be done here but works better with iteration as a image difference
- ** in the controling function (below). Thicken and Thinning however
- ** should be done here so thay can be iterated correctly.
- */
- switch ( method ) {
- case HitAndMissMorphology:
- case ErodeMorphology:
- result = min; /* minimum of neighbourhood */
- break;
- case DilateMorphology:
- result = max; /* maximum of neighbourhood */
- break;
- case ThinningMorphology:
- /* subtract pattern match from original */
- result.red -= min.red;
- result.green -= min.green;
- result.blue -= min.blue;
- result.black -= min.black;
- result.alpha -= min.alpha;
- break;
- case ThickenMorphology:
- /* Add the pattern matchs to the original */
- result.red += min.red;
- result.green += min.green;
- result.blue += min.blue;
- result.black += min.black;
- result.alpha += min.alpha;
- break;
- default:
- /* result directly calculated or assigned */
- break;
- }
- /* Assign the resulting pixel values - Clamping Result */
- switch ( method ) {
- case UndefinedMorphology:
- case ConvolveMorphology:
- case DilateIntensityMorphology:
- case ErodeIntensityMorphology:
- break; /* full pixel was directly assigned - not a channel method */
- default:
- if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
- SetPixelRed(morphology_image,ClampToQuantum(result.red),q);
- if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
- SetPixelGreen(morphology_image,ClampToQuantum(result.green),q);
- if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
- SetPixelBlue(morphology_image,ClampToQuantum(result.blue),q);
- if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
- (image->colorspace == CMYKColorspace))
- SetPixelBlack(morphology_image,ClampToQuantum(result.black),q);
- if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
- (image->alpha_trait == BlendPixelTrait))
- SetPixelAlpha(morphology_image,ClampToQuantum(result.alpha),q);
- break;
+ }
+ case UndefinedMorphology:
+ default:
+ break;
+ }
+ if (fabs(pixel-p[center+i]) > MagickEpsilon)
+ changed++;
+ SetPixelChannel(morphology_image,channel,ClampToQuantum(pixel),q);
}
- /* Count up changed pixels */
- if ((GetPixelRed(image,p+r) != GetPixelRed(morphology_image,q)) ||
- (GetPixelGreen(image,p+r) != GetPixelGreen(morphology_image,q)) ||
- (GetPixelBlue(image,p+r) != GetPixelBlue(morphology_image,q)) ||
- (GetPixelAlpha(image,p+r) != GetPixelAlpha(morphology_image,q)) ||
- ((image->colorspace == CMYKColorspace) &&
- (GetPixelBlack(image,p+r) != GetPixelBlack(morphology_image,q))))
- changed++; /* The pixel was changed in some way! */
p+=GetPixelChannels(image);
q+=GetPixelChannels(morphology_image);
- } /* x */
+ }
if ( SyncCacheViewAuthenticPixels(morphology_view,exception) == MagickFalse)
status=MagickFalse;
if (image->progress_monitor != (MagickProgressMonitor) NULL)
if (proceed == MagickFalse)
status=MagickFalse;
}
- } /* y */
+ }
morphology_view=DestroyCacheView(morphology_view);
image_view=DestroyCacheView(image_view);
return(status ? (ssize_t)changed : -1);
}
-/* This is almost identical to the MorphologyPrimative() function above,
-** but will apply the primitive directly to the actual image using two
-** passes, once in each direction, with the results of the previous (and
-** current) row being re-used.
-**
-** That is after each row is 'Sync'ed' into the image, the next row will
-** make use of those values as part of the calculation of the next row.
-** It then repeats, but going in the oppisite (bottom-up) direction.
-**
-** Because of this 're-use of results' this function can not make use
-** of multi-threaded, parellel processing.
+/*
+ This is almost identical to the MorphologyPrimative() function above, but
+ applies the primitive directly to the actual image using two passes, once in
+ each direction, with the results of the previous (and current) row being
+ re-used.
+
+ That is after each row is 'Sync'ed' into the image, the next row makes use of
+ those values as part of the calculation of the next row. It repeats, but
+ going in the oppisite (bottom-up) direction.
+
+ Because of this 're-use of results' this function can not make use of multi-
+ threaded, parellel processing.
*/
static ssize_t MorphologyPrimitiveDirect(Image *image,
const MorphologyMethod method,const KernelInfo *kernel,
ExceptionInfo *exception)
{
CacheView
- *auth_view,
- *virt_view;
+ *morphology_view,
+ *image_view;
MagickBooleanType
status;
MagickOffsetType
progress;
- ssize_t
- y, offx, offy;
+ OffsetInfo
+ offset;
size_t
- virt_width,
+ width,
changed;
- status=MagickTrue;
- changed=0;
- progress=0;
+ ssize_t
+ y;
assert(image != (Image *) NULL);
assert(image->signature == MagickSignature);
assert(kernel->signature == MagickSignature);
assert(exception != (ExceptionInfo *) NULL);
assert(exception->signature == MagickSignature);
-
- /* Some methods (including convolve) needs use a reflected kernel.
- * Adjust 'origin' offsets to loop though kernel as a reflection.
- */
- offx = kernel->x;
- offy = kernel->y;
- switch(method) {
+ status=MagickTrue;
+ changed=0;
+ progress=0;
+ switch(method)
+ {
case DistanceMorphology:
case VoronoiMorphology:
- /* kernel needs to used with reflection about origin */
- offx = (ssize_t) kernel->width-offx-1;
- offy = (ssize_t) kernel->height-offy-1;
- break;
-#if 0
- case ?????Morphology:
- /* kernel is used as is, without reflection */
+ {
+ /*
+ Kernel reflected about origin.
+ */
+ offset.x=(ssize_t) kernel->width-kernel->x-1;
+ offset.y=(ssize_t) kernel->height-kernel->y-1;
break;
-#endif
+ }
default:
- assert("Not a PrimativeDirect Morphology Method" != (char *) NULL);
+ {
+ offset.x=kernel->x;
+ offset.y=kernel->y;
break;
+ }
}
-
- /* DO NOT THREAD THIS CODE! */
- /* two views into same image (virtual, and actual) */
- virt_view=AcquireVirtualCacheView(image,exception);
- auth_view=AcquireAuthenticCacheView(image,exception);
- virt_width=image->columns+kernel->width-1;
-
+ /*
+ Two views into same image, do not thread.
+ */
+ image_view=AcquireVirtualCacheView(image,exception);
+ morphology_view=AcquireAuthenticCacheView(image,exception);
+ width=image->columns+kernel->width-1;
for (y=0; y < (ssize_t) image->rows; y++)
{
register const Quantum
x;
ssize_t
- r;
-
- /* NOTE read virtual pixels, and authentic pixels, from the same image!
- ** we read using virtual to get virtual pixel handling, but write back
- ** into the same image.
- **
- ** Only top half of kernel is processed as we do a single pass downward
- ** through the image iterating the distance function as we go.
+ center;
+
+ /*
+ Read virtual pixels, and authentic pixels, from the same image! We read
+ using virtual to get virtual pixel handling, but write back into the same
+ image.
+
+ Only top half of kernel is processed as we do a single pass downward
+ through the image iterating the distance function as we go.
*/
if (status == MagickFalse)
break;
- p=GetCacheViewVirtualPixels(virt_view,-offx,y-offy,virt_width,(size_t)
- offy+1,exception);
- q=GetCacheViewAuthenticPixels(auth_view, 0, y, image->columns, 1,
+ p=GetCacheViewVirtualPixels(image_view,-offset.x,y-offset.y,width,(size_t)
+ offset.y+1,exception);
+ q=GetCacheViewAuthenticPixels(morphology_view, 0, y, image->columns, 1,
exception);
if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
status=MagickFalse;
if (status == MagickFalse)
break;
-
- /* offset to origin in 'p'. while 'q' points to it directly */
- r = (ssize_t) GetPixelChannels(image)*virt_width*offy + GetPixelChannels(image)*offx;
-
+ center=(ssize_t) GetPixelChannels(image)*width*offset.y+
+ GetPixelChannels(image)*offset.x;
for (x=0; x < (ssize_t) image->columns; x++)
{
- PixelInfo
- result;
+ register ssize_t
+ i;
- register const MagickRealType
- *restrict k;
+ for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
+ {
+ double
+ pixel;
- register const Quantum
- *restrict k_pixels;
+ PixelChannel
+ channel;
- register ssize_t
- u;
+ PixelTrait
+ traits;
- ssize_t
- v;
-
- /* Starting Defaults */
- GetPixelInfo(image,&result);
- GetPixelInfoPixel(image,q,&result);
- if ( method != VoronoiMorphology )
- result.alpha = QuantumRange - result.alpha;
-
- switch ( method ) {
- case DistanceMorphology:
- /* Add kernel Value and select the minimum value found. */
- k = &kernel->values[ kernel->width*kernel->height-1 ];
- k_pixels = p;
- for (v=0; v <= (ssize_t) offy; v++) {
- for (u=0; u < (ssize_t) kernel->width; u++, k--) {
- if ( IsNaN(*k) ) continue;
- Minimize(result.red, (*k)+
- GetPixelRed(image,k_pixels+u*GetPixelChannels(image)));
- Minimize(result.green, (*k)+
- GetPixelGreen(image,k_pixels+u*GetPixelChannels(image)));
- Minimize(result.blue, (*k)+
- GetPixelBlue(image,k_pixels+u*GetPixelChannels(image)));
- if (image->colorspace == CMYKColorspace)
- Minimize(result.black,(*k)+
- GetPixelBlue(image,k_pixels+u*GetPixelChannels(image)));
- Minimize(result.alpha, (*k)+
- GetPixelAlpha(image,k_pixels+u*GetPixelChannels(image)));
- }
- k_pixels += virt_width*GetPixelChannels(image);
- }
- /* repeat with the just processed pixels of this row */
- k = &kernel->values[ kernel->width*(kernel->y+1)-1 ];
- k_pixels = q-offx*GetPixelChannels(image);
- for (u=0; u < (ssize_t) offx; u++, k--) {
- if ( x+u-offx < 0 ) continue; /* off the edge! */
- if ( IsNaN(*k) ) continue;
- Minimize(result.red, (*k)+
- GetPixelRed(image,k_pixels+u*GetPixelChannels(image)));
- Minimize(result.green, (*k)+
- GetPixelGreen(image,k_pixels+u*GetPixelChannels(image)));
- Minimize(result.blue, (*k)+
- GetPixelBlue(image,k_pixels+u*GetPixelChannels(image)));
- if (image->colorspace == CMYKColorspace)
- Minimize(result.black,(*k)+
- GetPixelBlack(image,k_pixels+u*GetPixelChannels(image)));
- Minimize(result.alpha,(*k)+
- GetPixelAlpha(image,k_pixels+u*GetPixelChannels(image)));
- }
- break;
- case VoronoiMorphology:
- /* Apply Distance to 'Matte' channel, while coping the color
- ** values of the closest pixel.
- **
- ** This is experimental, and realy the 'alpha' component should
- ** be completely separate 'masking' channel so that alpha can
- ** also be used as part of the results.
- */
- k = &kernel->values[ kernel->width*kernel->height-1 ];
- k_pixels = p;
- for (v=0; v <= (ssize_t) offy; v++) {
- for (u=0; u < (ssize_t) kernel->width; u++, k--) {
- if ( IsNaN(*k) ) continue;
- if( result.alpha > (*k)+GetPixelAlpha(image,k_pixels+u*GetPixelChannels(image)) )
+ register const MagickRealType
+ *restrict k;
+
+ register const Quantum
+ *restrict pixels;
+
+ register ssize_t
+ u;
+
+ ssize_t
+ v;
+
+ channel=GetPixelChannelChannel(image,i);
+ traits=GetPixelChannelTraits(image,channel);
+ if (traits == UndefinedPixelTrait)
+ continue;
+ if (((traits & CopyPixelTrait) != 0) || (GetPixelMask(image,p) != 0))
+ continue;
+ pixels=p;
+ pixel=(double) p[center+i];
+ switch (method)
+ {
+ case DistanceMorphology:
+ {
+ k=(&kernel->values[kernel->width*kernel->height-1]);
+ for (v=0; v <= offset.y; v++)
+ {
+ for (u=0; u < (ssize_t) kernel->width; u++)
+ {
+ if (IsNaN(*k) == MagickFalse)
{
- GetPixelInfoPixel(image,k_pixels+u*GetPixelChannels(image),
- &result);
- result.alpha += *k;
+ if ((pixels[i]+(*k)) < pixel)
+ pixel=(double) pixels[i]+(*k);
}
+ k--;
+ pixels+=GetPixelChannels(image);
}
- k_pixels += virt_width*GetPixelChannels(image);
+ pixels+=width*GetPixelChannels(image);
+ }
+ k=(&kernel->values[kernel->width*(kernel->y+1)-1]);
+ pixels=q-offset.x*GetPixelChannels(image);
+ for (u=0; u < offset.x; u++)
+ {
+ if ((IsNaN(*k) == MagickFalse) || ((x+u-offset.x) < 0))
+ {
+ if ((pixels[i]+(*k)) < pixel)
+ pixel=(double) pixels[i]+(*k);
+ }
+ k--;
+ pixels+=GetPixelChannels(image);
}
- /* repeat with the just processed pixels of this row */
- k = &kernel->values[ kernel->width*(kernel->y+1)-1 ];
- k_pixels = q-offx*GetPixelChannels(image);
- for (u=0; u < (ssize_t) offx; u++, k--) {
- if ( x+u-offx < 0 ) continue; /* off the edge! */
- if ( IsNaN(*k) ) continue;
- if( result.alpha > (*k)+GetPixelAlpha(image,k_pixels+u*GetPixelChannels(image)) )
+ break;
+ }
+ case VoronoiMorphology:
+ {
+ k=(&kernel->values[kernel->width*kernel->height-1]);
+ for (v=0; v < offset.y; v++)
+ {
+ for (u=0; u < (ssize_t) kernel->width; u++)
+ {
+ if (IsNaN(*k) == MagickFalse)
{
- GetPixelInfoPixel(image,k_pixels+u*GetPixelChannels(image),
- &result);
- result.alpha += *k;
+ if ((pixels[i]+(*k)) < pixel)
+ pixel=(double) pixels[i]+(*k);
}
+ k--;
+ pixels+=GetPixelChannels(image);
}
+ pixels+=width*GetPixelChannels(image);
+ }
+ k=(&kernel->values[kernel->width*(kernel->y+1)-1]);
+ pixels=q-offset.x*GetPixelChannels(image);
+ for (u=0; u < offset.x; u++)
+ {
+ if ((IsNaN(*k) == MagickFalse) || ((x+u-offset.x) < 0))
+ {
+ if ((pixels[i]+(*k)) < pixel)
+ pixel=(double) pixels[i]+(*k);
+ }
+ k--;
+ pixels+=GetPixelChannels(image);
+ }
break;
- default:
- /* result directly calculated or assigned */
- break;
- }
- /* Assign the resulting pixel values - Clamping Result */
- switch ( method ) {
- case VoronoiMorphology:
- SetPixelInfoPixel(image,&result,q);
- break;
- default:
- if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
- SetPixelRed(image,ClampToQuantum(result.red),q);
- if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
- SetPixelGreen(image,ClampToQuantum(result.green),q);
- if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
- SetPixelBlue(image,ClampToQuantum(result.blue),q);
- if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
- (image->colorspace == CMYKColorspace))
- SetPixelBlack(image,ClampToQuantum(result.black),q);
- if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0 &&
- (image->alpha_trait == BlendPixelTrait))
- SetPixelAlpha(image,ClampToQuantum(result.alpha),q);
- break;
+ }
+ default:
+ break;
+ }
+ if (fabs(pixel-p[center+i]) > MagickEpsilon)
+ changed++;
+ SetPixelChannel(image,channel,ClampToQuantum(pixel),q);
}
- /* Count up changed pixels */
- if ((GetPixelRed(image,p+r) != GetPixelRed(image,q)) ||
- (GetPixelGreen(image,p+r) != GetPixelGreen(image,q)) ||
- (GetPixelBlue(image,p+r) != GetPixelBlue(image,q)) ||
- (GetPixelAlpha(image,p+r) != GetPixelAlpha(image,q)) ||
- ((image->colorspace == CMYKColorspace) &&
- (GetPixelBlack(image,p+r) != GetPixelBlack(image,q))))
- changed++; /* The pixel was changed in some way! */
-
- p+=GetPixelChannels(image); /* increment pixel buffers */
+ p+=GetPixelChannels(image);
q+=GetPixelChannels(image);
- } /* x */
-
- if ( SyncCacheViewAuthenticPixels(auth_view,exception) == MagickFalse)
+ }
+ if ( SyncCacheViewAuthenticPixels(morphology_view,exception) == MagickFalse)
status=MagickFalse;
if (image->progress_monitor != (MagickProgressMonitor) NULL)
- if ( SetImageProgress(image,MorphologyTag,progress++,image->rows)
- == MagickFalse )
- status=MagickFalse;
-
- } /* y */
+ {
+ MagickBooleanType
+ proceed;
- /* Do the reversed pass through the image */
- for (y=(ssize_t)image->rows-1; y >= 0; y--)
+#if defined(MAGICKCORE_OPENMP_SUPPORT)
+ #pragma omp critical (MagickCore_MorphologyImage)
+#endif
+ proceed=SetImageProgress(image,MorphologyTag,progress++,image->rows);
+ if (proceed == MagickFalse)
+ status=MagickFalse;
+ }
+ }
+ /*
+ Do the reverse pass through the image.
+ */
+ for (y=(ssize_t) image->rows-1; y >= 0; y--)
{
register const Quantum
*restrict p;
x;
ssize_t
- r;
+ center;
+ /*
+ Read virtual pixels, and authentic pixels, from the same image. We
+ read using virtual to get virtual pixel handling, but write back
+ into the same image.
+
+ Only the bottom half of the kernel is processed as we up the image.
+ */
if (status == MagickFalse)
break;
- /* NOTE read virtual pixels, and authentic pixels, from the same image!
- ** we read using virtual to get virtual pixel handling, but write back
- ** into the same image.
- **
- ** Only the bottom half of the kernel will be processes as we
- ** up the image.
- */
- p=GetCacheViewVirtualPixels(virt_view,-offx,y,virt_width,(size_t)
+ p=GetCacheViewVirtualPixels(image_view,-offset.x,y,width,(size_t)
kernel->y+1,exception);
- q=GetCacheViewAuthenticPixels(auth_view, 0, y, image->columns, 1,
+ q=GetCacheViewAuthenticPixels(morphology_view, 0, y, image->columns, 1,
exception);
if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
status=MagickFalse;
if (status == MagickFalse)
break;
+ p+=(image->columns-1)*GetPixelChannels(image);
+ q+=(image->columns-1)*GetPixelChannels(image);
+ center=(ssize_t) (GetPixelChannels(image)*offset.x);
+ for (x=(ssize_t)image->columns-1; x >= 0; x--)
+ {
+ register ssize_t
+ i;
- /* adjust positions to end of row */
- p += (image->columns-1)*GetPixelChannels(image);
- q += (image->columns-1)*GetPixelChannels(image);
+ for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
+ {
+ double
+ pixel;
- /* offset to origin in 'p'. while 'q' points to it directly */
- r = GetPixelChannels(image)*offx;
+ PixelChannel
+ channel;
- for (x=(ssize_t)image->columns-1; x >= 0; x--)
- {
- PixelInfo
- result;
+ PixelTrait
+ traits;
- register const MagickRealType
- *restrict k;
+ register const MagickRealType
+ *restrict k;
- register const Quantum
- *restrict k_pixels;
+ register const Quantum
+ *restrict pixels;
- register ssize_t
- u;
+ register ssize_t
+ u;
- ssize_t
- v;
-
- /* Default - previously modified pixel */
- GetPixelInfo(image,&result);
- GetPixelInfoPixel(image,q,&result);
- if ( method != VoronoiMorphology )
- result.alpha = QuantumRange - result.alpha;
-
- switch ( method ) {
- case DistanceMorphology:
- /* Add kernel Value and select the minimum value found. */
- k = &kernel->values[ kernel->width*(kernel->y+1)-1 ];
- k_pixels = p;
- for (v=offy; v < (ssize_t) kernel->height; v++) {
- for (u=0; u < (ssize_t) kernel->width; u++, k--) {
- if ( IsNaN(*k) ) continue;
- Minimize(result.red, (*k)+
- GetPixelRed(image,k_pixels+u*GetPixelChannels(image)));
- Minimize(result.green, (*k)+
- GetPixelGreen(image,k_pixels+u*GetPixelChannels(image)));
- Minimize(result.blue, (*k)+
- GetPixelBlue(image,k_pixels+u*GetPixelChannels(image)));
- if ( image->colorspace == CMYKColorspace)
- Minimize(result.black,(*k)+
- GetPixelBlack(image,k_pixels+u*GetPixelChannels(image)));
- Minimize(result.alpha, (*k)+
- GetPixelAlpha(image,k_pixels+u*GetPixelChannels(image)));
- }
- k_pixels += virt_width*GetPixelChannels(image);
- }
- /* repeat with the just processed pixels of this row */
- k = &kernel->values[ kernel->width*(kernel->y)+kernel->x-1 ];
- k_pixels = q-offx*GetPixelChannels(image);
- for (u=offx+1; u < (ssize_t) kernel->width; u++, k--) {
- if ( (x+u-offx) >= (ssize_t)image->columns ) continue;
- if ( IsNaN(*k) ) continue;
- Minimize(result.red, (*k)+
- GetPixelRed(image,k_pixels+u*GetPixelChannels(image)));
- Minimize(result.green, (*k)+
- GetPixelGreen(image,k_pixels+u*GetPixelChannels(image)));
- Minimize(result.blue, (*k)+
- GetPixelBlue(image,k_pixels+u*GetPixelChannels(image)));
- if ( image->colorspace == CMYKColorspace)
- Minimize(result.black, (*k)+
- GetPixelBlack(image,k_pixels+u*GetPixelChannels(image)));
- Minimize(result.alpha, (*k)+
- GetPixelAlpha(image,k_pixels+u*GetPixelChannels(image)));
- }
- break;
- case VoronoiMorphology:
- /* Apply Distance to 'Matte' channel, coping the closest color.
- **
- ** This is experimental, and realy the 'alpha' component should
- ** be completely separate 'masking' channel.
- */
- k = &kernel->values[ kernel->width*(kernel->y+1)-1 ];
- k_pixels = p;
- for (v=offy; v < (ssize_t) kernel->height; v++) {
- for (u=0; u < (ssize_t) kernel->width; u++, k--) {
- if ( IsNaN(*k) ) continue;
- if( result.alpha > (*k)+GetPixelAlpha(image,k_pixels+u*GetPixelChannels(image)) )
+ ssize_t
+ v;
+
+ channel=GetPixelChannelChannel(image,i);
+ traits=GetPixelChannelTraits(image,channel);
+ if (traits == UndefinedPixelTrait)
+ continue;
+ if (((traits & CopyPixelTrait) != 0) || (GetPixelMask(image,p) != 0))
+ continue;
+ pixels=p;
+ pixel=(double) p[center+i];
+ switch (method)
+ {
+ case DistanceMorphology:
+ {
+ k=(&kernel->values[kernel->width*(kernel->y+1)-1]);
+ for (v=offset.y; v < (ssize_t) kernel->height; v++)
+ {
+ for (u=0; u < (ssize_t) kernel->width; u++)
+ {
+ if (IsNaN(*k) == MagickFalse)
{
- GetPixelInfoPixel(image,k_pixels+u*GetPixelChannels(image),
- &result);
- result.alpha += *k;
+ if ((pixels[i]+(*k)) < pixel)
+ pixel=(double) pixels[i]+(*k);
}
+ k--;
+ pixels+=GetPixelChannels(image);
}
- k_pixels += virt_width*GetPixelChannels(image);
+ pixels+=width*GetPixelChannels(image);
}
- /* repeat with the just processed pixels of this row */
- k = &kernel->values[ kernel->width*(kernel->y)+kernel->x-1 ];
- k_pixels = q-offx*GetPixelChannels(image);
- for (u=offx+1; u < (ssize_t) kernel->width; u++, k--) {
- if ( (x+u-offx) >= (ssize_t)image->columns ) continue;
- if ( IsNaN(*k) ) continue;
- if( result.alpha > (*k)+GetPixelAlpha(image,k_pixels+u*GetPixelChannels(image)) )
+ k=(&kernel->values[kernel->width*kernel->y+kernel->x-1]);
+ pixels=q-offset.x*GetPixelChannels(image);
+ for (u=offset.x+1; u < (ssize_t) kernel->width; u++)
+ {
+ if ((IsNaN(*k) == MagickFalse) ||
+ ((x+u-offset.x) >= (ssize_t)image->columns))
+ {
+ if ((pixels[i]+(*k)) < pixel)
+ pixel=(double) pixels[i]+(*k);
+ }
+ k--;
+ pixels+=GetPixelChannels(image);
+ }
+ break;
+ }
+ case VoronoiMorphology:
+ {
+ k=(&kernel->values[kernel->width*(kernel->y+1)-1]);
+ for (v=offset.y; v < (ssize_t) kernel->height; v++)
+ {
+ for (u=0; u < (ssize_t) kernel->width; u++)
+ {
+ if (IsNaN(*k) == MagickFalse)
{
- GetPixelInfoPixel(image,k_pixels+u*GetPixelChannels(image),
- &result);
- result.alpha += *k;
+ if ((pixels[i]+(*k)) < pixel)
+ pixel=(double) pixels[i]+(*k);
}
+ k--;
+ pixels+=GetPixelChannels(image);
}
+ pixels+=width*GetPixelChannels(image);
+ }
+ k=(&kernel->values[kernel->width*(kernel->y+1)-1]);
+ pixels=q-offset.x*GetPixelChannels(image);
+ for (u=offset.x+1; u < (ssize_t) kernel->width; u++)
+ {
+ if ((IsNaN(*k) == MagickFalse) || ((x+u-offset.x) < 0))
+ {
+ if ((pixels[i]+(*k)) < pixel)
+ pixel=(double) pixels[i]+(*k);
+ }
+ k--;
+ pixels+=GetPixelChannels(image);
+ }
break;
- default:
- /* result directly calculated or assigned */
- break;
- }
- /* Assign the resulting pixel values - Clamping Result */
- switch ( method ) {
- case VoronoiMorphology:
- SetPixelInfoPixel(image,&result,q);
- break;
- default:
- if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
- SetPixelRed(image,ClampToQuantum(result.red),q);
- if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
- SetPixelGreen(image,ClampToQuantum(result.green),q);
- if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
- SetPixelBlue(image,ClampToQuantum(result.blue),q);
- if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
- (image->colorspace == CMYKColorspace))
- SetPixelBlack(image,ClampToQuantum(result.black),q);
- if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0 &&
- (image->alpha_trait == BlendPixelTrait))
- SetPixelAlpha(image,ClampToQuantum(result.alpha),q);
- break;
+ }
+ default:
+ break;
+ }
+ if (fabs(pixel-p[center+i]) > MagickEpsilon)
+ changed++;
+ SetPixelChannel(image,channel,ClampToQuantum(pixel),q);
}
- /* Count up changed pixels */
- if ( (GetPixelRed(image,p+r) != GetPixelRed(image,q))
- || (GetPixelGreen(image,p+r) != GetPixelGreen(image,q))
- || (GetPixelBlue(image,p+r) != GetPixelBlue(image,q))
- || (GetPixelAlpha(image,p+r) != GetPixelAlpha(image,q))
- || ((image->colorspace == CMYKColorspace) &&
- (GetPixelBlack(image,p+r) != GetPixelBlack(image,q))))
- changed++; /* The pixel was changed in some way! */
-
- p-=GetPixelChannels(image); /* go backward through pixel buffers */
- q-=GetPixelChannels(image);
- } /* x */
- if ( SyncCacheViewAuthenticPixels(auth_view,exception) == MagickFalse)
- status=MagickFalse;
- if (image->progress_monitor != (MagickProgressMonitor) NULL)
- if ( SetImageProgress(image,MorphologyTag,progress++,image->rows)
- == MagickFalse )
- status=MagickFalse;
-
- } /* y */
-
- auth_view=DestroyCacheView(auth_view);
- virt_view=DestroyCacheView(virt_view);
+ p+=GetPixelChannels(image);
+ q+=GetPixelChannels(image);
+ }
+ }
+ morphology_view=DestroyCacheView(morphology_view);
+ image_view=DestroyCacheView(image_view);
return(status ? (ssize_t) changed : -1);
}
-/* Apply a Morphology by calling one of the above low level primitive
-** application functions. This function handles any iteration loops,
-** composition or re-iteration of results, and compound morphology methods
-** that is based on multiple low-level (staged) morphology methods.
-**
-** Basically this provides the complex glue between the requested morphology
-** method and raw low-level implementation (above).
+/*
+ Apply a Morphology by calling one of the above low level primitive
+ application functions. This function handles any iteration loops,
+ composition or re-iteration of results, and compound morphology methods that
+ is based on multiple low-level (staged) morphology methods.
+
+ Basically this provides the complex glue between the requested morphology
+ method and raw low-level implementation (above).
*/
MagickPrivate Image *MorphologyApply(const Image *image,
const MorphologyMethod method, const ssize_t iterations,