mirror of
https://github.com/ONLYOFFICE/core.git
synced 2026-07-21 10:11:16 +08:00
git-svn-id: svn://fileserver/activex/AVS/Sources/TeamlabOffice/trunk/ServerComponents@62634 954022d7-b5bf-4e40-9824-e11837661b57
1031 lines
34 KiB
C++
1031 lines
34 KiB
C++
/*====================================================================*
|
|
- Copyright (C) 2001 Leptonica. All rights reserved.
|
|
- This software is distributed in the hope that it will be
|
|
- useful, but with NO WARRANTY OF ANY KIND.
|
|
- No author or distributor accepts responsibility to anyone for the
|
|
- consequences of using this software, or for whether it serves any
|
|
- particular purpose or works at all, unless he or she says so in
|
|
- writing. Everyone is granted permission to copy, modify and
|
|
- redistribute this source code, for commercial or non-commercial
|
|
- purposes, with the following restrictions: (1) the origin of this
|
|
- source code must not be misrepresented; (2) modified versions must
|
|
- be plainly marked as such; and (3) this notice may not be removed
|
|
- or altered from any source or modified source distribution.
|
|
*====================================================================*/
|
|
|
|
|
|
/*
|
|
* graymorph.c
|
|
*
|
|
* Top-level binary morphological operations (van Herk / Gil-Werman)
|
|
* PIX *pixErodeGray()
|
|
* PIX *pixDilateGray()
|
|
* PIX *pixOpenGray()
|
|
* PIX *pixCloseGray()
|
|
*
|
|
* Special operations for 1x3, 3x1 and 3x3 Sels (direct)
|
|
* PIX *pixErodeGray3()
|
|
* PIX *pixDilateGray3()
|
|
* PIX *pixOpenGray3()
|
|
* PIX *pixCloseGray3()
|
|
*
|
|
*
|
|
* Method: Algorithm by van Herk and Gil and Werman, 1992
|
|
*
|
|
* Measured speed of the vH/G-W implementation is about 1 output
|
|
* pixel per 120 PIII clock cycles, for a horizontal or vertical
|
|
* erosion or dilation. The computation time doubles for opening
|
|
* or closing, or for a square SE, as expected, and is independent
|
|
* of the size of the SE.
|
|
*
|
|
* A faster implementation can be made directly for brick Sels
|
|
* of maximum size 3. We unroll the computation for sets of 8 bytes.
|
|
* It needs to be called explicitly; the general functions do not
|
|
* default for the size 3 brick Sels.
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include "allheaders.h"
|
|
|
|
static PIX *pixErodeGray3h(PIX *pixs);
|
|
static PIX *pixErodeGray3v(PIX *pixs);
|
|
static PIX *pixDilateGray3h(PIX *pixs);
|
|
static PIX *pixDilateGray3v(PIX *pixs);
|
|
|
|
|
|
/*-----------------------------------------------------------------*
|
|
* Top-level gray morphological operations *
|
|
*-----------------------------------------------------------------*/
|
|
/*!
|
|
* pixErodeGray()
|
|
*
|
|
* Input: pixs
|
|
* hsize (of Sel; must be odd; origin implicitly in center)
|
|
* vsize (ditto)
|
|
* Return: pixd
|
|
*
|
|
* Notes:
|
|
* (1) Sel is a brick with all elements being hits
|
|
* (2) If hsize = vsize = 1, just returns a copy.
|
|
*/
|
|
PIX *
|
|
pixErodeGray(PIX *pixs,
|
|
l_int32 hsize,
|
|
l_int32 vsize)
|
|
{
|
|
l_uint8 *buffer, *minarray;
|
|
l_int32 w, h, wplb, wplt;
|
|
l_int32 leftpix, rightpix, toppix, bottompix, maxsize;
|
|
l_uint32 *datab, *datat;
|
|
PIX *pixb, *pixt, *pixd;
|
|
|
|
PROCNAME("pixErodeGray");
|
|
|
|
if (!pixs)
|
|
return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
|
|
if (pixGetDepth(pixs) != 8)
|
|
return (PIX *)ERROR_PTR("pixs not 8 bpp", procName, NULL);
|
|
if (hsize < 1 || vsize < 1)
|
|
return (PIX *)ERROR_PTR("hsize or vsize < 1", procName, NULL);
|
|
if ((hsize & 1) == 0 ) {
|
|
L_WARNING("horiz sel size must be odd; increasing by 1", procName);
|
|
hsize++;
|
|
}
|
|
if ((vsize & 1) == 0 ) {
|
|
L_WARNING("vert sel size must be odd; increasing by 1", procName);
|
|
vsize++;
|
|
}
|
|
|
|
if (hsize == 1 && vsize == 1)
|
|
return pixCopy(NULL, pixs);
|
|
|
|
if (vsize == 1) { /* horizontal sel */
|
|
leftpix = (hsize + 1) / 2;
|
|
rightpix = (3 * hsize + 1) / 2;
|
|
toppix = 0;
|
|
bottompix = 0;
|
|
}
|
|
else if (hsize == 1) { /* vertical sel */
|
|
leftpix = 0;
|
|
rightpix = 0;
|
|
toppix = (vsize + 1) / 2;
|
|
bottompix = (3 * vsize + 1) / 2;
|
|
}
|
|
else {
|
|
leftpix = (hsize + 1) / 2;
|
|
rightpix = (3 * hsize + 1) / 2;
|
|
toppix = (vsize + 1) / 2;
|
|
bottompix = (3 * vsize + 1) / 2;
|
|
}
|
|
|
|
if ((pixb = pixAddBorderGeneral(pixs,
|
|
leftpix, rightpix, toppix, bottompix, 255)) == NULL)
|
|
return (PIX *)ERROR_PTR("pixb not made", procName, NULL);
|
|
if ((pixt = pixCreateTemplate(pixb)) == NULL)
|
|
return (PIX *)ERROR_PTR("pixt not made", procName, NULL);
|
|
|
|
pixGetDimensions(pixt, &w, &h, NULL);
|
|
datab = pixGetData(pixb);
|
|
datat = pixGetData(pixt);
|
|
wplb = pixGetWpl(pixb);
|
|
wplt = pixGetWpl(pixt);
|
|
|
|
if ((buffer = (l_uint8 *)CALLOC(L_MAX(w, h), sizeof(l_uint8))) == NULL)
|
|
return (PIX *)ERROR_PTR("buffer not made", procName, NULL);
|
|
maxsize = L_MAX(hsize, vsize);
|
|
if ((minarray = (l_uint8 *)CALLOC(2 * maxsize, sizeof(l_uint8))) == NULL)
|
|
return (PIX *)ERROR_PTR("minarray not made", procName, NULL);
|
|
|
|
if (vsize == 1)
|
|
erodeGrayLow(datat, w, h, wplt, datab, wplb, hsize, L_HORIZ,
|
|
buffer, minarray);
|
|
else if (hsize == 1)
|
|
erodeGrayLow(datat, w, h, wplt, datab, wplb, vsize, L_VERT,
|
|
buffer, minarray);
|
|
else {
|
|
erodeGrayLow(datat, w, h, wplt, datab, wplb, hsize, L_HORIZ,
|
|
buffer, minarray);
|
|
pixSetOrClearBorder(pixt, leftpix, rightpix, toppix, bottompix,
|
|
PIX_SET);
|
|
erodeGrayLow(datab, w, h, wplb, datat, wplt, vsize, L_VERT,
|
|
buffer, minarray);
|
|
pixDestroy(&pixt);
|
|
pixt = pixClone(pixb);
|
|
}
|
|
|
|
if ((pixd = pixRemoveBorderGeneral(pixt,
|
|
leftpix, rightpix, toppix, bottompix)) == NULL)
|
|
return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
|
|
|
|
FREE(buffer);
|
|
FREE(minarray);
|
|
pixDestroy(&pixb);
|
|
pixDestroy(&pixt);
|
|
return pixd;
|
|
}
|
|
|
|
|
|
/*!
|
|
* pixDilateGray()
|
|
*
|
|
* Input: pixs
|
|
* hsize (of Sel; must be odd; origin implicitly in center)
|
|
* vsize (ditto)
|
|
* Return: pixd
|
|
*
|
|
* Notes:
|
|
* (1) Sel is a brick with all elements being hits
|
|
* (2) If hsize = vsize = 1, just returns a copy.
|
|
*/
|
|
PIX *
|
|
pixDilateGray(PIX *pixs,
|
|
l_int32 hsize,
|
|
l_int32 vsize)
|
|
{
|
|
l_uint8 *buffer, *maxarray;
|
|
l_int32 w, h, wplb, wplt;
|
|
l_int32 leftpix, rightpix, toppix, bottompix, maxsize;
|
|
l_uint32 *datab, *datat;
|
|
PIX *pixb, *pixt, *pixd;
|
|
|
|
PROCNAME("pixDilateGray");
|
|
|
|
if (!pixs)
|
|
return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
|
|
if (pixGetDepth(pixs) != 8)
|
|
return (PIX *)ERROR_PTR("pixs not 8 bpp", procName, NULL);
|
|
if (hsize < 1 || vsize < 1)
|
|
return (PIX *)ERROR_PTR("hsize or vsize < 1", procName, NULL);
|
|
if ((hsize & 1) == 0 ) {
|
|
L_WARNING("horiz sel size must be odd; increasing by 1", procName);
|
|
hsize++;
|
|
}
|
|
if ((vsize & 1) == 0 ) {
|
|
L_WARNING("vert sel size must be odd; increasing by 1", procName);
|
|
vsize++;
|
|
}
|
|
|
|
if (hsize == 1 && vsize == 1)
|
|
return pixCopy(NULL, pixs);
|
|
|
|
if (vsize == 1) { /* horizontal sel */
|
|
leftpix = (hsize + 1) / 2;
|
|
rightpix = (3 * hsize + 1) / 2;
|
|
toppix = 0;
|
|
bottompix = 0;
|
|
}
|
|
else if (hsize == 1) { /* vertical sel */
|
|
leftpix = 0;
|
|
rightpix = 0;
|
|
toppix = (vsize + 1) / 2;
|
|
bottompix = (3 * vsize + 1) / 2;
|
|
}
|
|
else {
|
|
leftpix = (hsize + 1) / 2;
|
|
rightpix = (3 * hsize + 1) / 2;
|
|
toppix = (vsize + 1) / 2;
|
|
bottompix = (3 * vsize + 1) / 2;
|
|
}
|
|
|
|
if ((pixb = pixAddBorderGeneral(pixs,
|
|
leftpix, rightpix, toppix, bottompix, 0)) == NULL)
|
|
return (PIX *)ERROR_PTR("pixb not made", procName, NULL);
|
|
if ((pixt = pixCreateTemplate(pixb)) == NULL)
|
|
return (PIX *)ERROR_PTR("pixt not made", procName, NULL);
|
|
|
|
pixGetDimensions(pixt, &w, &h, NULL);
|
|
datab = pixGetData(pixb);
|
|
datat = pixGetData(pixt);
|
|
wplb = pixGetWpl(pixb);
|
|
wplt = pixGetWpl(pixt);
|
|
|
|
if ((buffer = (l_uint8 *)CALLOC(L_MAX(w, h), sizeof(l_uint8))) == NULL)
|
|
return (PIX *)ERROR_PTR("buffer not made", procName, NULL);
|
|
maxsize = L_MAX(hsize, vsize);
|
|
if ((maxarray = (l_uint8 *)CALLOC(2 * maxsize, sizeof(l_uint8))) == NULL)
|
|
return (PIX *)ERROR_PTR("buffer not made", procName, NULL);
|
|
|
|
if (vsize == 1)
|
|
dilateGrayLow(datat, w, h, wplt, datab, wplb, hsize, L_HORIZ,
|
|
buffer, maxarray);
|
|
else if (hsize == 1)
|
|
dilateGrayLow(datat, w, h, wplt, datab, wplb, vsize, L_VERT,
|
|
buffer, maxarray);
|
|
else {
|
|
dilateGrayLow(datat, w, h, wplt, datab, wplb, hsize, L_HORIZ,
|
|
buffer, maxarray);
|
|
pixSetOrClearBorder(pixt, leftpix, rightpix, toppix, bottompix,
|
|
PIX_CLR);
|
|
dilateGrayLow(datab, w, h, wplb, datat, wplt, vsize, L_VERT,
|
|
buffer, maxarray);
|
|
pixDestroy(&pixt);
|
|
pixt = pixClone(pixb);
|
|
}
|
|
|
|
if ((pixd = pixRemoveBorderGeneral(pixt,
|
|
leftpix, rightpix, toppix, bottompix)) == NULL)
|
|
return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
|
|
|
|
FREE(buffer);
|
|
FREE(maxarray);
|
|
pixDestroy(&pixb);
|
|
pixDestroy(&pixt);
|
|
return pixd;
|
|
}
|
|
|
|
|
|
/*!
|
|
* pixOpenGray()
|
|
*
|
|
* Input: pixs
|
|
* hsize (of Sel; must be odd; origin implicitly in center)
|
|
* vsize (ditto)
|
|
* Return: pixd
|
|
*
|
|
* Notes:
|
|
* (1) Sel is a brick with all elements being hits
|
|
* (2) If hsize = vsize = 1, just returns a copy.
|
|
*/
|
|
PIX *
|
|
pixOpenGray(PIX *pixs,
|
|
l_int32 hsize,
|
|
l_int32 vsize)
|
|
{
|
|
l_uint8 *buffer;
|
|
l_uint8 *array; /* used to find either min or max in interval */
|
|
l_int32 w, h, wplb, wplt;
|
|
l_int32 leftpix, rightpix, toppix, bottompix, maxsize;
|
|
l_uint32 *datab, *datat;
|
|
PIX *pixb, *pixt, *pixd;
|
|
|
|
PROCNAME("pixOpenGray");
|
|
|
|
if (!pixs)
|
|
return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
|
|
if (pixGetDepth(pixs) != 8)
|
|
return (PIX *)ERROR_PTR("pixs not 8 bpp", procName, NULL);
|
|
if (hsize < 1 || vsize < 1)
|
|
return (PIX *)ERROR_PTR("hsize or vsize < 1", procName, NULL);
|
|
if ((hsize & 1) == 0 ) {
|
|
L_WARNING("horiz sel size must be odd; increasing by 1", procName);
|
|
hsize++;
|
|
}
|
|
if ((vsize & 1) == 0 ) {
|
|
L_WARNING("vert sel size must be odd; increasing by 1", procName);
|
|
vsize++;
|
|
}
|
|
|
|
if (hsize == 1 && vsize == 1)
|
|
return pixCopy(NULL, pixs);
|
|
|
|
if (vsize == 1) { /* horizontal sel */
|
|
leftpix = (hsize + 1) / 2;
|
|
rightpix = (3 * hsize + 1) / 2;
|
|
toppix = 0;
|
|
bottompix = 0;
|
|
}
|
|
else if (hsize == 1) { /* vertical sel */
|
|
leftpix = 0;
|
|
rightpix = 0;
|
|
toppix = (vsize + 1) / 2;
|
|
bottompix = (3 * vsize + 1) / 2;
|
|
}
|
|
else {
|
|
leftpix = (hsize + 1) / 2;
|
|
rightpix = (3 * hsize + 1) / 2;
|
|
toppix = (vsize + 1) / 2;
|
|
bottompix = (3 * vsize + 1) / 2;
|
|
}
|
|
|
|
if ((pixb = pixAddBorderGeneral(pixs,
|
|
leftpix, rightpix, toppix, bottompix, 255)) == NULL)
|
|
return (PIX *)ERROR_PTR("pixb not made", procName, NULL);
|
|
if ((pixt = pixCreateTemplate(pixb)) == NULL)
|
|
return (PIX *)ERROR_PTR("pixt not made", procName, NULL);
|
|
|
|
pixGetDimensions(pixt, &w, &h, NULL);
|
|
datab = pixGetData(pixb);
|
|
datat = pixGetData(pixt);
|
|
wplb = pixGetWpl(pixb);
|
|
wplt = pixGetWpl(pixt);
|
|
|
|
if ((buffer = (l_uint8 *)CALLOC(L_MAX(w, h), sizeof(l_uint8))) == NULL)
|
|
return (PIX *)ERROR_PTR("buffer not made", procName, NULL);
|
|
maxsize = L_MAX(hsize, vsize);
|
|
if ((array = (l_uint8 *)CALLOC(2 * maxsize, sizeof(l_uint8))) == NULL)
|
|
return (PIX *)ERROR_PTR("array not made", procName, NULL);
|
|
|
|
if (vsize == 1) {
|
|
erodeGrayLow(datat, w, h, wplt, datab, wplb, hsize, L_HORIZ,
|
|
buffer, array);
|
|
pixSetOrClearBorder(pixt, leftpix, rightpix, toppix, bottompix,
|
|
PIX_CLR);
|
|
dilateGrayLow(datab, w, h, wplb, datat, wplt, hsize, L_HORIZ,
|
|
buffer, array);
|
|
}
|
|
else if (hsize == 1) {
|
|
erodeGrayLow(datat, w, h, wplt, datab, wplb, vsize, L_VERT,
|
|
buffer, array);
|
|
pixSetOrClearBorder(pixt, leftpix, rightpix, toppix, bottompix,
|
|
PIX_CLR);
|
|
dilateGrayLow(datab, w, h, wplb, datat, wplt, vsize, L_VERT,
|
|
buffer, array);
|
|
}
|
|
else {
|
|
erodeGrayLow(datat, w, h, wplt, datab, wplb, hsize, L_HORIZ,
|
|
buffer, array);
|
|
pixSetOrClearBorder(pixt, leftpix, rightpix, toppix, bottompix,
|
|
PIX_SET);
|
|
erodeGrayLow(datab, w, h, wplb, datat, wplt, vsize, L_VERT,
|
|
buffer, array);
|
|
pixSetOrClearBorder(pixb, leftpix, rightpix, toppix, bottompix,
|
|
PIX_CLR);
|
|
dilateGrayLow(datat, w, h, wplt, datab, wplb, hsize, L_HORIZ,
|
|
buffer, array);
|
|
pixSetOrClearBorder(pixt, leftpix, rightpix, toppix, bottompix,
|
|
PIX_CLR);
|
|
dilateGrayLow(datab, w, h, wplb, datat, wplt, vsize, L_VERT,
|
|
buffer, array);
|
|
}
|
|
|
|
if ((pixd = pixRemoveBorderGeneral(pixb,
|
|
leftpix, rightpix, toppix, bottompix)) == NULL)
|
|
return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
|
|
|
|
FREE(buffer);
|
|
FREE(array);
|
|
pixDestroy(&pixb);
|
|
pixDestroy(&pixt);
|
|
return pixd;
|
|
}
|
|
|
|
|
|
/*!
|
|
* pixCloseGray()
|
|
*
|
|
* Input: pixs
|
|
* hsize (of Sel; must be odd; origin implicitly in center)
|
|
* vsize (ditto)
|
|
* Return: pixd
|
|
*
|
|
* Notes:
|
|
* (1) Sel is a brick with all elements being hits
|
|
* (2) If hsize = vsize = 1, just returns a copy.
|
|
*/
|
|
PIX *
|
|
pixCloseGray(PIX *pixs,
|
|
l_int32 hsize,
|
|
l_int32 vsize)
|
|
{
|
|
l_uint8 *buffer;
|
|
l_uint8 *array; /* used to find either min or max in interval */
|
|
l_int32 w, h, wplb, wplt;
|
|
l_int32 leftpix, rightpix, toppix, bottompix, maxsize;
|
|
l_uint32 *datab, *datat;
|
|
PIX *pixb, *pixt, *pixd;
|
|
|
|
PROCNAME("pixCloseGray");
|
|
|
|
if (!pixs)
|
|
return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
|
|
if (pixGetDepth(pixs) != 8)
|
|
return (PIX *)ERROR_PTR("pixs not 8 bpp", procName, NULL);
|
|
if (hsize < 1 || vsize < 1)
|
|
return (PIX *)ERROR_PTR("hsize or vsize < 1", procName, NULL);
|
|
if ((hsize & 1) == 0 ) {
|
|
L_WARNING("horiz sel size must be odd; increasing by 1", procName);
|
|
hsize++;
|
|
}
|
|
if ((vsize & 1) == 0 ) {
|
|
L_WARNING("vert sel size must be odd; increasing by 1", procName);
|
|
vsize++;
|
|
}
|
|
|
|
if (hsize == 1 && vsize == 1)
|
|
return pixCopy(NULL, pixs);
|
|
|
|
if (vsize == 1) { /* horizontal sel */
|
|
leftpix = (hsize + 1) / 2;
|
|
rightpix = (3 * hsize + 1) / 2;
|
|
toppix = 0;
|
|
bottompix = 0;
|
|
}
|
|
else if (hsize == 1) { /* vertical sel */
|
|
leftpix = 0;
|
|
rightpix = 0;
|
|
toppix = (vsize + 1) / 2;
|
|
bottompix = (3 * vsize + 1) / 2;
|
|
}
|
|
else {
|
|
leftpix = (hsize + 1) / 2;
|
|
rightpix = (3 * hsize + 1) / 2;
|
|
toppix = (vsize + 1) / 2;
|
|
bottompix = (3 * vsize + 1) / 2;
|
|
}
|
|
|
|
if ((pixb = pixAddBorderGeneral(pixs,
|
|
leftpix, rightpix, toppix, bottompix, 0)) == NULL)
|
|
return (PIX *)ERROR_PTR("pixb not made", procName, NULL);
|
|
if ((pixt = pixCreateTemplate(pixb)) == NULL)
|
|
return (PIX *)ERROR_PTR("pixt not made", procName, NULL);
|
|
|
|
pixGetDimensions(pixt, &w, &h, NULL);
|
|
datab = pixGetData(pixb);
|
|
datat = pixGetData(pixt);
|
|
wplb = pixGetWpl(pixb);
|
|
wplt = pixGetWpl(pixt);
|
|
|
|
if ((buffer = (l_uint8 *)CALLOC(L_MAX(w, h), sizeof(l_uint8))) == NULL)
|
|
return (PIX *)ERROR_PTR("buffer not made", procName, NULL);
|
|
maxsize = L_MAX(hsize, vsize);
|
|
if ((array = (l_uint8 *)CALLOC(2 * maxsize, sizeof(l_uint8))) == NULL)
|
|
return (PIX *)ERROR_PTR("array not made", procName, NULL);
|
|
|
|
if (vsize == 1) {
|
|
dilateGrayLow(datat, w, h, wplt, datab, wplb, hsize, L_HORIZ,
|
|
buffer, array);
|
|
pixSetOrClearBorder(pixt, leftpix, rightpix, toppix, bottompix,
|
|
PIX_SET);
|
|
erodeGrayLow(datab, w, h, wplb, datat, wplt, hsize, L_HORIZ,
|
|
buffer, array);
|
|
}
|
|
else if (hsize == 1) {
|
|
dilateGrayLow(datat, w, h, wplt, datab, wplb, vsize, L_VERT,
|
|
buffer, array);
|
|
pixSetOrClearBorder(pixt, leftpix, rightpix, toppix, bottompix,
|
|
PIX_SET);
|
|
erodeGrayLow(datab, w, h, wplb, datat, wplt, vsize, L_VERT,
|
|
buffer, array);
|
|
}
|
|
else {
|
|
dilateGrayLow(datat, w, h, wplt, datab, wplb, hsize, L_HORIZ,
|
|
buffer, array);
|
|
pixSetOrClearBorder(pixt, leftpix, rightpix, toppix, bottompix,
|
|
PIX_CLR);
|
|
dilateGrayLow(datab, w, h, wplb, datat, wplt, vsize, L_VERT,
|
|
buffer, array);
|
|
pixSetOrClearBorder(pixb, leftpix, rightpix, toppix, bottompix,
|
|
PIX_SET);
|
|
erodeGrayLow(datat, w, h, wplt, datab, wplb, hsize, L_HORIZ,
|
|
buffer, array);
|
|
pixSetOrClearBorder(pixt, leftpix, rightpix, toppix, bottompix,
|
|
PIX_SET);
|
|
erodeGrayLow(datab, w, h, wplb, datat, wplt, vsize, L_VERT,
|
|
buffer, array);
|
|
}
|
|
|
|
if ((pixd = pixRemoveBorderGeneral(pixb,
|
|
leftpix, rightpix, toppix, bottompix)) == NULL)
|
|
return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
|
|
|
|
FREE(buffer);
|
|
FREE(array);
|
|
pixDestroy(&pixb);
|
|
pixDestroy(&pixt);
|
|
return pixd;
|
|
}
|
|
|
|
|
|
/*-----------------------------------------------------------------*
|
|
* Special operations for 1x3, 3x1 and 3x3 Sels *
|
|
*-----------------------------------------------------------------*/
|
|
/*!
|
|
* pixErodeGray3()
|
|
*
|
|
* Input: pixs (8 bpp, not cmapped)
|
|
* hsize (1 or 3)
|
|
* vsize (1 or 3)
|
|
* Return: pixd, or null on error
|
|
*
|
|
* Notes:
|
|
* (1) Special case for 1x3, 3x1 or 3x3 brick sel (all hits)
|
|
* (2) If hsize = vsize = 1, just returns a copy.
|
|
* (3) It would be nice not to add a border, but it is required
|
|
* if we want the same results as from the general case.
|
|
* We add 4 bytes on the left to speed up the copying, and
|
|
* 8 bytes at the right and bottom to allow unrolling of
|
|
* the computation of 8 pixels.
|
|
*/
|
|
PIX *
|
|
pixErodeGray3(PIX *pixs,
|
|
l_int32 hsize,
|
|
l_int32 vsize)
|
|
{
|
|
PIX *pixt, *pixb, *pixbd, *pixd;
|
|
|
|
PROCNAME("pixErodeGray3");
|
|
|
|
if (!pixs)
|
|
return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
|
|
if (pixGetDepth(pixs) != 8)
|
|
return (PIX *)ERROR_PTR("pixs not 8 bpp", procName, NULL);
|
|
if (pixGetColormap(pixs))
|
|
return (PIX *)ERROR_PTR("pix has colormap", procName, NULL);
|
|
if ((hsize != 1 && hsize != 3) ||
|
|
(vsize != 1 && vsize != 3))
|
|
return (PIX *)ERROR_PTR("invalid size: must be 1 or 3", procName, NULL);
|
|
|
|
if (hsize == 1 && vsize == 1)
|
|
return pixCopy(NULL, pixs);
|
|
|
|
pixb = pixAddBorderGeneral(pixs, 4, 8, 2, 8, 255);
|
|
|
|
if (vsize == 1)
|
|
pixbd = pixErodeGray3h(pixb);
|
|
else if (hsize == 1)
|
|
pixbd = pixErodeGray3v(pixb);
|
|
else { /* vize == hsize == 3 */
|
|
pixt = pixErodeGray3h(pixb);
|
|
pixbd = pixErodeGray3v(pixt);
|
|
pixDestroy(&pixt);
|
|
}
|
|
|
|
pixd = pixRemoveBorderGeneral(pixbd, 4, 8, 2, 8);
|
|
pixDestroy(&pixb);
|
|
pixDestroy(&pixbd);
|
|
return pixd;
|
|
}
|
|
|
|
|
|
/*!
|
|
* pixErodeGray3h()
|
|
*
|
|
* Input: pixs (8 bpp, not cmapped)
|
|
* Return: pixd, or null on error
|
|
*
|
|
* Notes:
|
|
* (1) Special case for horizontal 3x1 brick Sel;
|
|
* also used as the first step for the 3x3 brick Sel.
|
|
*/
|
|
static PIX *
|
|
pixErodeGray3h(PIX *pixs)
|
|
{
|
|
l_uint32 *datas, *datad, *lines, *lined;
|
|
l_int32 w, h, wpl, i, j;
|
|
l_int32 val0, val1, val2, val3, val4, val5, val6, val7, val8, val9, minval;
|
|
PIX *pixd;
|
|
|
|
PROCNAME("pixErodeGray3h");
|
|
|
|
if (!pixs)
|
|
return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
|
|
if (pixGetDepth(pixs) != 8)
|
|
return (PIX *)ERROR_PTR("pixs not 8 bpp", procName, NULL);
|
|
|
|
pixd = pixCreateTemplateNoInit(pixs);
|
|
pixSetBorderVal(pixd, 4, 8, 2, 8, 0); /* only to silence valgrind */
|
|
pixGetDimensions(pixs, &w, &h, NULL);
|
|
datas = pixGetData(pixs);
|
|
datad = pixGetData(pixd);
|
|
wpl = pixGetWpl(pixs);
|
|
for (i = 0; i < h; i++) {
|
|
lines = datas + i * wpl;
|
|
lined = datad + i * wpl;
|
|
for (j = 1; j < w - 8; j += 8) {
|
|
val0 = GET_DATA_BYTE(lines, j - 1);
|
|
val1 = GET_DATA_BYTE(lines, j);
|
|
val2 = GET_DATA_BYTE(lines, j + 1);
|
|
val3 = GET_DATA_BYTE(lines, j + 2);
|
|
val4 = GET_DATA_BYTE(lines, j + 3);
|
|
val5 = GET_DATA_BYTE(lines, j + 4);
|
|
val6 = GET_DATA_BYTE(lines, j + 5);
|
|
val7 = GET_DATA_BYTE(lines, j + 6);
|
|
val8 = GET_DATA_BYTE(lines, j + 7);
|
|
val9 = GET_DATA_BYTE(lines, j + 8);
|
|
minval = L_MIN(val1, val2);
|
|
SET_DATA_BYTE(lined, j, L_MIN(val0, minval));
|
|
SET_DATA_BYTE(lined, j + 1, L_MIN(minval, val3));
|
|
minval = L_MIN(val3, val4);
|
|
SET_DATA_BYTE(lined, j + 2, L_MIN(val2, minval));
|
|
SET_DATA_BYTE(lined, j + 3, L_MIN(minval, val5));
|
|
minval = L_MIN(val5, val6);
|
|
SET_DATA_BYTE(lined, j + 4, L_MIN(val4, minval));
|
|
SET_DATA_BYTE(lined, j + 5, L_MIN(minval, val7));
|
|
minval = L_MIN(val7, val8);
|
|
SET_DATA_BYTE(lined, j + 6, L_MIN(val6, minval));
|
|
SET_DATA_BYTE(lined, j + 7, L_MIN(minval, val9));
|
|
}
|
|
}
|
|
return pixd;
|
|
}
|
|
|
|
|
|
/*!
|
|
* pixErodeGray3v()
|
|
*
|
|
* Input: pixs (8 bpp, not cmapped)
|
|
* Return: pixd, or null on error
|
|
*
|
|
* Notes:
|
|
* (1) Special case for vertical 1x3 brick Sel;
|
|
* also used as the second step for the 3x3 brick Sel.
|
|
* (2) Surprisingly, this is faster than setting up the
|
|
* lineptrs array and accessing into it; e.g.,
|
|
* val4 = GET_DATA_BYTE(lines8[i + 3], j);
|
|
*/
|
|
static PIX *
|
|
pixErodeGray3v(PIX *pixs)
|
|
{
|
|
l_uint32 *datas, *datad, *linesi, *linedi;
|
|
l_int32 w, h, wpl, i, j;
|
|
l_int32 val0, val1, val2, val3, val4, val5, val6, val7, val8, val9, minval;
|
|
PIX *pixd;
|
|
|
|
PROCNAME("pixErodeGray3v");
|
|
|
|
if (!pixs)
|
|
return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
|
|
if (pixGetDepth(pixs) != 8)
|
|
return (PIX *)ERROR_PTR("pixs not 8 bpp", procName, NULL);
|
|
|
|
pixd = pixCreateTemplateNoInit(pixs);
|
|
pixGetDimensions(pixs, &w, &h, NULL);
|
|
datas = pixGetData(pixs);
|
|
datad = pixGetData(pixd);
|
|
wpl = pixGetWpl(pixs);
|
|
for (j = 0; j < w; j++) {
|
|
for (i = 1; i < h - 8; i += 8) {
|
|
linesi = datas + i * wpl;
|
|
linedi = datad + i * wpl;
|
|
val0 = GET_DATA_BYTE(linesi - wpl, j);
|
|
val1 = GET_DATA_BYTE(linesi, j);
|
|
val2 = GET_DATA_BYTE(linesi + wpl, j);
|
|
val3 = GET_DATA_BYTE(linesi + 2 * wpl, j);
|
|
val4 = GET_DATA_BYTE(linesi + 3 * wpl, j);
|
|
val5 = GET_DATA_BYTE(linesi + 4 * wpl, j);
|
|
val6 = GET_DATA_BYTE(linesi + 5 * wpl, j);
|
|
val7 = GET_DATA_BYTE(linesi + 6 * wpl, j);
|
|
val8 = GET_DATA_BYTE(linesi + 7 * wpl, j);
|
|
val9 = GET_DATA_BYTE(linesi + 8 * wpl, j);
|
|
minval = L_MIN(val1, val2);
|
|
SET_DATA_BYTE(linedi, j, L_MIN(val0, minval));
|
|
SET_DATA_BYTE(linedi + wpl, j, L_MIN(minval, val3));
|
|
minval = L_MIN(val3, val4);
|
|
SET_DATA_BYTE(linedi + 2 * wpl, j, L_MIN(val2, minval));
|
|
SET_DATA_BYTE(linedi + 3 * wpl, j, L_MIN(minval, val5));
|
|
minval = L_MIN(val5, val6);
|
|
SET_DATA_BYTE(linedi + 4 * wpl, j, L_MIN(val4, minval));
|
|
SET_DATA_BYTE(linedi + 5 * wpl, j, L_MIN(minval, val7));
|
|
minval = L_MIN(val7, val8);
|
|
SET_DATA_BYTE(linedi + 6 * wpl, j, L_MIN(val6, minval));
|
|
SET_DATA_BYTE(linedi + 7 * wpl, j, L_MIN(minval, val9));
|
|
}
|
|
}
|
|
return pixd;
|
|
}
|
|
|
|
|
|
/*!
|
|
* pixDilateGray3()
|
|
*
|
|
* Input: pixs (8 bpp, not cmapped)
|
|
* hsize (1 or 3)
|
|
* vsize (1 or 3)
|
|
* Return: pixd, or null on error
|
|
*
|
|
* Notes:
|
|
* (1) Special case for 1x3, 3x1 or 3x3 brick sel (all hits)
|
|
* (2) If hsize = vsize = 1, just returns a copy.
|
|
*/
|
|
PIX *
|
|
pixDilateGray3(PIX *pixs,
|
|
l_int32 hsize,
|
|
l_int32 vsize)
|
|
{
|
|
PIX *pixt, *pixb, *pixbd, *pixd;
|
|
|
|
PROCNAME("pixDilateGray3");
|
|
|
|
if (!pixs)
|
|
return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
|
|
if (pixGetDepth(pixs) != 8)
|
|
return (PIX *)ERROR_PTR("pixs not 8 bpp", procName, NULL);
|
|
if (pixGetColormap(pixs))
|
|
return (PIX *)ERROR_PTR("pix has colormap", procName, NULL);
|
|
if ((hsize != 1 && hsize != 3) ||
|
|
(vsize != 1 && vsize != 3))
|
|
return (PIX *)ERROR_PTR("invalid size: must be 1 or 3", procName, NULL);
|
|
|
|
if (hsize == 1 && vsize == 1)
|
|
return pixCopy(NULL, pixs);
|
|
|
|
pixb = pixAddBorderGeneral(pixs, 4, 8, 2, 8, 0);
|
|
|
|
if (vsize == 1)
|
|
pixbd = pixDilateGray3h(pixb);
|
|
else if (hsize == 1)
|
|
pixbd = pixDilateGray3v(pixb);
|
|
else { /* vize == hsize == 3 */
|
|
pixt = pixDilateGray3h(pixb);
|
|
pixbd = pixDilateGray3v(pixt);
|
|
pixDestroy(&pixt);
|
|
}
|
|
|
|
pixd = pixRemoveBorderGeneral(pixbd, 4, 8, 2, 8);
|
|
pixDestroy(&pixb);
|
|
pixDestroy(&pixbd);
|
|
return pixd;
|
|
}
|
|
|
|
|
|
/*!
|
|
* pixDilateGray3h()
|
|
*
|
|
* Input: pixs (8 bpp, not cmapped)
|
|
* Return: pixd, or null on error
|
|
*
|
|
* Notes:
|
|
* (1) Special case for horizontal 3x1 brick Sel;
|
|
* also used as the first step for the 3x3 brick Sel.
|
|
*/
|
|
static PIX *
|
|
pixDilateGray3h(PIX *pixs)
|
|
{
|
|
l_uint32 *datas, *datad, *lines, *lined;
|
|
l_int32 w, h, wpl, i, j;
|
|
l_int32 val0, val1, val2, val3, val4, val5, val6, val7, val8, val9, maxval;
|
|
PIX *pixd;
|
|
|
|
PROCNAME("pixDilateGray3h");
|
|
|
|
if (!pixs)
|
|
return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
|
|
if (pixGetDepth(pixs) != 8)
|
|
return (PIX *)ERROR_PTR("pixs not 8 bpp", procName, NULL);
|
|
|
|
pixd = pixCreateTemplateNoInit(pixs);
|
|
pixSetBorderVal(pixd, 4, 8, 2, 8, 0); /* only to silence valgrind */
|
|
pixGetDimensions(pixs, &w, &h, NULL);
|
|
datas = pixGetData(pixs);
|
|
datad = pixGetData(pixd);
|
|
wpl = pixGetWpl(pixs);
|
|
for (i = 0; i < h; i++) {
|
|
lines = datas + i * wpl;
|
|
lined = datad + i * wpl;
|
|
for (j = 1; j < w - 8; j += 8) {
|
|
val0 = GET_DATA_BYTE(lines, j - 1);
|
|
val1 = GET_DATA_BYTE(lines, j);
|
|
val2 = GET_DATA_BYTE(lines, j + 1);
|
|
val3 = GET_DATA_BYTE(lines, j + 2);
|
|
val4 = GET_DATA_BYTE(lines, j + 3);
|
|
val5 = GET_DATA_BYTE(lines, j + 4);
|
|
val6 = GET_DATA_BYTE(lines, j + 5);
|
|
val7 = GET_DATA_BYTE(lines, j + 6);
|
|
val8 = GET_DATA_BYTE(lines, j + 7);
|
|
val9 = GET_DATA_BYTE(lines, j + 8);
|
|
maxval = L_MAX(val1, val2);
|
|
SET_DATA_BYTE(lined, j, L_MAX(val0, maxval));
|
|
SET_DATA_BYTE(lined, j + 1, L_MAX(maxval, val3));
|
|
maxval = L_MAX(val3, val4);
|
|
SET_DATA_BYTE(lined, j + 2, L_MAX(val2, maxval));
|
|
SET_DATA_BYTE(lined, j + 3, L_MAX(maxval, val5));
|
|
maxval = L_MAX(val5, val6);
|
|
SET_DATA_BYTE(lined, j + 4, L_MAX(val4, maxval));
|
|
SET_DATA_BYTE(lined, j + 5, L_MAX(maxval, val7));
|
|
maxval = L_MAX(val7, val8);
|
|
SET_DATA_BYTE(lined, j + 6, L_MAX(val6, maxval));
|
|
SET_DATA_BYTE(lined, j + 7, L_MAX(maxval, val9));
|
|
}
|
|
}
|
|
return pixd;
|
|
}
|
|
|
|
|
|
/*!
|
|
* pixDilateGray3v()
|
|
*
|
|
* Input: pixs (8 bpp, not cmapped)
|
|
* Return: pixd, or null on error
|
|
*
|
|
* Notes:
|
|
* (1) Special case for vertical 1x3 brick Sel;
|
|
* also used as the second step for the 3x3 brick Sel.
|
|
*/
|
|
static PIX *
|
|
pixDilateGray3v(PIX *pixs)
|
|
{
|
|
l_uint32 *datas, *datad, *linesi, *linedi;
|
|
l_int32 w, h, wpl, i, j;
|
|
l_int32 val0, val1, val2, val3, val4, val5, val6, val7, val8, val9, maxval;
|
|
PIX *pixd;
|
|
|
|
PROCNAME("pixDilateGray3v");
|
|
|
|
if (!pixs)
|
|
return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
|
|
if (pixGetDepth(pixs) != 8)
|
|
return (PIX *)ERROR_PTR("pixs not 8 bpp", procName, NULL);
|
|
|
|
pixd = pixCreateTemplateNoInit(pixs);
|
|
pixGetDimensions(pixs, &w, &h, NULL);
|
|
datas = pixGetData(pixs);
|
|
datad = pixGetData(pixd);
|
|
wpl = pixGetWpl(pixs);
|
|
for (j = 0; j < w; j++) {
|
|
for (i = 1; i < h - 8; i += 8) {
|
|
linesi = datas + i * wpl;
|
|
linedi = datad + i * wpl;
|
|
val0 = GET_DATA_BYTE(linesi - wpl, j);
|
|
val1 = GET_DATA_BYTE(linesi, j);
|
|
val2 = GET_DATA_BYTE(linesi + wpl, j);
|
|
val3 = GET_DATA_BYTE(linesi + 2 * wpl, j);
|
|
val4 = GET_DATA_BYTE(linesi + 3 * wpl, j);
|
|
val5 = GET_DATA_BYTE(linesi + 4 * wpl, j);
|
|
val6 = GET_DATA_BYTE(linesi + 5 * wpl, j);
|
|
val7 = GET_DATA_BYTE(linesi + 6 * wpl, j);
|
|
val8 = GET_DATA_BYTE(linesi + 7 * wpl, j);
|
|
val9 = GET_DATA_BYTE(linesi + 8 * wpl, j);
|
|
maxval = L_MAX(val1, val2);
|
|
SET_DATA_BYTE(linedi, j, L_MAX(val0, maxval));
|
|
SET_DATA_BYTE(linedi + wpl, j, L_MAX(maxval, val3));
|
|
maxval = L_MAX(val3, val4);
|
|
SET_DATA_BYTE(linedi + 2 * wpl, j, L_MAX(val2, maxval));
|
|
SET_DATA_BYTE(linedi + 3 * wpl, j, L_MAX(maxval, val5));
|
|
maxval = L_MAX(val5, val6);
|
|
SET_DATA_BYTE(linedi + 4 * wpl, j, L_MAX(val4, maxval));
|
|
SET_DATA_BYTE(linedi + 5 * wpl, j, L_MAX(maxval, val7));
|
|
maxval = L_MAX(val7, val8);
|
|
SET_DATA_BYTE(linedi + 6 * wpl, j, L_MAX(val6, maxval));
|
|
SET_DATA_BYTE(linedi + 7 * wpl, j, L_MAX(maxval, val9));
|
|
}
|
|
}
|
|
return pixd;
|
|
}
|
|
|
|
|
|
/*!
|
|
* pixOpenGray3()
|
|
*
|
|
* Input: pixs (8 bpp, not cmapped)
|
|
* hsize (1 or 3)
|
|
* vsize (1 or 3)
|
|
* Return: pixd, or null on error
|
|
*
|
|
* Notes:
|
|
* (1) Special case for 1x3, 3x1 or 3x3 brick sel (all hits)
|
|
* (2) If hsize = vsize = 1, just returns a copy.
|
|
* (3) It would be nice not to add a border, but it is required
|
|
* to get the same results as for the general case.
|
|
*/
|
|
PIX *
|
|
pixOpenGray3(PIX *pixs,
|
|
l_int32 hsize,
|
|
l_int32 vsize)
|
|
{
|
|
PIX *pixt, *pixb, *pixbd, *pixd;
|
|
|
|
PROCNAME("pixOpenGray3");
|
|
|
|
if (!pixs)
|
|
return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
|
|
if (pixGetDepth(pixs) != 8)
|
|
return (PIX *)ERROR_PTR("pixs not 8 bpp", procName, NULL);
|
|
if (pixGetColormap(pixs))
|
|
return (PIX *)ERROR_PTR("pix has colormap", procName, NULL);
|
|
if ((hsize != 1 && hsize != 3) ||
|
|
(vsize != 1 && vsize != 3))
|
|
return (PIX *)ERROR_PTR("invalid size: must be 1 or 3", procName, NULL);
|
|
|
|
if (hsize == 1 && vsize == 1)
|
|
return pixCopy(NULL, pixs);
|
|
|
|
pixb = pixAddBorderGeneral(pixs, 4, 8, 2, 8, 255); /* set to max */
|
|
|
|
if (vsize == 1) {
|
|
pixt = pixErodeGray3h(pixb);
|
|
pixSetBorderVal(pixt, 4, 8, 2, 8, 0); /* set to min */
|
|
pixbd = pixDilateGray3h(pixt);
|
|
pixDestroy(&pixt);
|
|
}
|
|
else if (hsize == 1) {
|
|
pixt = pixErodeGray3v(pixb);
|
|
pixSetBorderVal(pixt, 4, 8, 2, 8, 0);
|
|
pixbd = pixDilateGray3v(pixt);
|
|
pixDestroy(&pixt);
|
|
}
|
|
else { /* vize == hsize == 3 */
|
|
pixt = pixErodeGray3h(pixb);
|
|
pixbd = pixErodeGray3v(pixt);
|
|
pixDestroy(&pixt);
|
|
pixSetBorderVal(pixbd, 4, 8, 2, 8, 0);
|
|
pixt = pixDilateGray3h(pixbd);
|
|
pixDestroy(&pixbd);
|
|
pixbd = pixDilateGray3v(pixt);
|
|
pixDestroy(&pixt);
|
|
}
|
|
|
|
pixd = pixRemoveBorderGeneral(pixbd, 4, 8, 2, 8);
|
|
pixDestroy(&pixb);
|
|
pixDestroy(&pixbd);
|
|
return pixd;
|
|
}
|
|
|
|
|
|
/*!
|
|
* pixCloseGray3()
|
|
*
|
|
* Input: pixs (8 bpp, not cmapped)
|
|
* hsize (1 or 3)
|
|
* vsize (1 or 3)
|
|
* Return: pixd, or null on error
|
|
*
|
|
* Notes:
|
|
* (1) Special case for 1x3, 3x1 or 3x3 brick sel (all hits)
|
|
* (2) If hsize = vsize = 1, just returns a copy.
|
|
*/
|
|
PIX *
|
|
pixCloseGray3(PIX *pixs,
|
|
l_int32 hsize,
|
|
l_int32 vsize)
|
|
{
|
|
PIX *pixt, *pixb, *pixbd, *pixd;
|
|
|
|
PROCNAME("pixCloseGray3");
|
|
|
|
if (!pixs)
|
|
return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
|
|
if (pixGetDepth(pixs) != 8)
|
|
return (PIX *)ERROR_PTR("pixs not 8 bpp", procName, NULL);
|
|
if (pixGetColormap(pixs))
|
|
return (PIX *)ERROR_PTR("pix has colormap", procName, NULL);
|
|
if ((hsize != 1 && hsize != 3) ||
|
|
(vsize != 1 && vsize != 3))
|
|
return (PIX *)ERROR_PTR("invalid size: must be 1 or 3", procName, NULL);
|
|
|
|
if (hsize == 1 && vsize == 1)
|
|
return pixCopy(NULL, pixs);
|
|
|
|
pixb = pixAddBorderGeneral(pixs, 4, 8, 2, 8, 0); /* set to min */
|
|
|
|
if (vsize == 1) {
|
|
pixt = pixDilateGray3h(pixb);
|
|
pixSetBorderVal(pixt, 4, 8, 2, 8, 255); /* set to max */
|
|
pixbd = pixErodeGray3h(pixt);
|
|
pixDestroy(&pixt);
|
|
}
|
|
else if (hsize == 1) {
|
|
pixt = pixDilateGray3v(pixb);
|
|
pixSetBorderVal(pixt, 4, 8, 2, 8, 255);
|
|
pixbd = pixErodeGray3v(pixt);
|
|
pixDestroy(&pixt);
|
|
}
|
|
else { /* vize == hsize == 3 */
|
|
pixt = pixDilateGray3h(pixb);
|
|
pixbd = pixDilateGray3v(pixt);
|
|
pixDestroy(&pixt);
|
|
pixSetBorderVal(pixbd, 4, 8, 2, 8, 255);
|
|
pixt = pixErodeGray3h(pixbd);
|
|
pixDestroy(&pixbd);
|
|
pixbd = pixErodeGray3v(pixt);
|
|
pixDestroy(&pixt);
|
|
}
|
|
|
|
pixd = pixRemoveBorderGeneral(pixbd, 4, 8, 2, 8);
|
|
pixDestroy(&pixb);
|
|
pixDestroy(&pixbd);
|
|
return pixd;
|
|
}
|
|
|
|
|