Skip to content

Commit

Permalink
Improved pixCropImage() for cropping pdf images
Browse files Browse the repository at this point in the history
* Split out pixRescaleForCropping().  Now works properly to add a boundary
  to prevent losing pixels when printed.
  • Loading branch information
DanBloomberg committed May 12, 2024
1 parent 76c635c commit 46784f4
Show file tree
Hide file tree
Showing 4 changed files with 151 additions and 69 deletions.
2 changes: 1 addition & 1 deletion prog/compresspdf.c
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@ SARRAY *sa;
lept_stderr("compressing ...\n");
compressFilesToPdf(sa, onebit, savecolor, scalefactor, quality,
title, fileout);

sarrayDestroy(&sa);
return 0;
}

2 changes: 1 addition & 1 deletion prog/croppdf.c
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ SARRAY *sa;
lept_stderr("cropping ...\n");
cropFilesToPdf(sa, lrclear, tbclear, edgeclean, lradd, tbadd, maxwiden,
title, fileout);

sarrayDestroy(&sa);
return 0;
}

3 changes: 2 additions & 1 deletion src/allheaders.h
Original file line number Diff line number Diff line change
Expand Up @@ -1376,7 +1376,8 @@ LEPT_DLL extern PIX * pixGenHalftoneMask ( PIX *pixs, PIX **ppixtext, l_int32 *p
LEPT_DLL extern PIX * pixGenerateHalftoneMask ( PIX *pixs, PIX **ppixtext, l_int32 *phtfound, PIXA *pixadb );
LEPT_DLL extern PIX * pixGenTextlineMask ( PIX *pixs, PIX **ppixvws, l_int32 *ptlfound, PIXA *pixadb );
LEPT_DLL extern PIX * pixGenTextblockMask ( PIX *pixs, PIX *pixvws, PIXA *pixadb );
LEPT_DLL extern PIX * pixCropImage ( PIX *pixs, l_int32 lr_clear, l_int32 tb_clear, l_int32 edgeclean, l_int32 lr_add, l_int32 tb_add, l_float32 maxwiden, const char *debugfile, BOX **pcropbox );
LEPT_DLL extern PIX * pixCropImage ( PIX *pixs, l_int32 lr_clear, l_int32 tb_clear, l_int32 edgeclean, l_int32 lr_border, l_int32 tb_border, l_float32 maxwiden, const char *debugfile, BOX **pcropbox );
LEPT_DLL extern PIX * pixRescaleForCropping ( PIX *pixs, l_int32 w, l_int32 h, l_int32 lr_border, l_int32 tb_border, l_float32 maxwiden, PIX **ppixsc );
LEPT_DLL extern PIX * pixCleanImage ( PIX *pixs, l_int32 contrast, l_int32 rotation, l_int32 scale, l_int32 opensize );
LEPT_DLL extern BOX * pixFindPageForeground ( PIX *pixs, l_int32 threshold, l_int32 mindist, l_int32 erasedist, l_int32 showmorph, PIXAC *pixac );
LEPT_DLL extern l_ok pixSplitIntoCharacters ( PIX *pixs, l_int32 minw, l_int32 minh, BOXA **pboxa, PIXA **ppixa, PIX **ppixdebug );
Expand Down
213 changes: 147 additions & 66 deletions src/pageseg.c
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
*
* Location and extraction of page foreground; cleaning pages
* PIX *pixCropImage()
* PIX *pixRescaleForCropping()
* PIX *pixCleanImage()
* BOX *pixFindPageForeground()
*
Expand Down Expand Up @@ -536,58 +537,59 @@ PIX *pix1, *pix2, *pix3, *pixd;
* \param[in] edgeclean parameter for removing edge noise (0-15)
* default = 0 (no removal);
* 15 is maximally aggressive
* \param[in] lr_add full res expansion of crop box on left and right
* \param[in] tb_add full res expansion of crop box on top and bottom
* \param[in] lr_border full res final "added" pixels on left and right
* \param[in] tb_border full res final "added" pixels on top and bottom
* \param[in] maxwiden max fractional horizontal stretch allowed
* \param[in] *debugfile [optional] usually is NULL
* \param[out] *pcropbox [optional] crop box at full resolution
* \return cropped pix, or NULL on error
*
* <pre>
* Notes:
* (1) This binarizes and crops a page image. After binarization
* and 2x reduction, it does the following:
* (a) Clears near the border by %lr_clear/2 and %tb_clear/ pixels
* (b) If %edgeclean > 0, it removes isolated sets of pixels,
* (1) This binarizes and crops a page image.
* (a) Binarizes if necessary and does 2x reduction.
* (b) Clears near the border by %lr_clear/2 and %tb_clear/2 pixels
* (c) If %edgeclean > 0, it removes isolated sets of pixels,
* using a close/open operation of size %edgeclean + 1.
* (c) Find the bounding box of remaining fg pixels
* (d) Expands the box by %lr_add and and %tb_add pixels,
* but respecting the cleared regions.
* (e) 2x expansion of the bounding box to full resolution.
* (f) Crops the binarized image to the bounding box.
* (g) Slightly thickens long horizontal lines.
* (h) Does anamorphic horizontal upscaling by %maxwiden
* to better fill an 8.5 x 11 inch printed page.
* Suggest not to exceed 1.15.
* Note that input parameters are given at full resolution and
* (a) - (c) are done at 2x reduction for efficiency.
* (2) The side clearing must not exceed 1/6 of the dimension on that side.
* (3) The clear and add pixel parameters must be >= 0.
* (4) This is not intended to work on small thumbnails. The
* (d) Find the bounding box of remaining fg pixels and scales
* the box up 2x back to full resolution.
* (e) Crops the binarized image to the bounding box.
* (f) Slightly thickens long horizontal lines.
* (g) Rescales this image to fit within the original image
* less lr_border on the sides and tb_border above and below.
* The rescaling is done isomorphically with a (possible)
* optional additional widening. Suggest the additional
* widening factor not exceed 1.15.
* Note that (b) - (d) are done at 2x reduction for efficiency.
* (2) Side clearing must not exceed 1/6 of the dimension on that side.
* (3) The clear and border pixel parameters must be >= 0.
* (4) The "clear" parameters act on the input image, whereas the
* "border" parameters act to give a white border to the final
* image. They are not literally added, because the input and final
* images are the same size.
* (5) This is not intended to work on small thumbnails. The
* dimensions of pixs must be at least MinWidth x MinHeight.
* (5) Step (g) above helps with orthographically-produced music notation,
* where the staff lines can be very thin and subject to printer alias.
* (6) Step (f) above helps with orthographically-produced music notation,
* where the horizontal staff lines can be very thin and thus
* subject to printer alias.
* </pre>
*/
PIX *
pixCropImage(PIX *pixs,
l_int32 lr_clear,
l_int32 tb_clear,
l_int32 edgeclean,
l_int32 lr_add,
l_int32 tb_add,
l_int32 lr_border,
l_int32 tb_border,
l_float32 maxwiden,
const char *debugfile,
BOX **pcropbox)
{
char cmd[64];
l_int32 w, h, d, val;
l_int32 left, right, top, bot, leftfinal, rightfinal, topfinal, botfinal;
static l_int32 first_time = TRUE;
l_float32 hscale;
PIX *pix1, *pix2, *pix3;
PIXA *pixa1;
BOX *box1, *box2;
char cmd[64];
l_int32 w, h, val;
BOX *box1, *box2;
PIX *pix1, *pix2, *pix3;
PIXA *pixa1;

if (pcropbox) *pcropbox = NULL;
if (!pixs)
Expand All @@ -597,22 +599,22 @@ BOX *box1, *box2;
L_WARNING("edgeclean > 15; setting to 15\n", __func__);
edgeclean = 15;
}
pixGetDimensions(pixs, &w, &h, &d);
pixGetDimensions(pixs, &w, &h, NULL);
if (w < MinWidth || h < MinHeight) {
L_ERROR("pix too small: w = %d, h = %d\n", __func__, w, h);
return NULL;
}
if (lr_clear < 0) lr_clear = 0;
if (tb_clear < 0) tb_clear = 0;
if (lr_add < 0) lr_add = 0;
if (tb_add < 0) tb_add = 0;
if (lr_border < 0) lr_border = 0;
if (tb_border < 0) tb_border = 0;
if (lr_clear > w / 6 || tb_clear > h / 6) {
L_ERROR("lr_clear or tb_clear too large; must be <= %d and %d\n",
__func__, w / 6, h / 6);
return NULL;
}
if (maxwiden > 1.2)
L_WARNING("maxwiden = %f > 1.2; suggest between 1.0 and 1.15\n",
if (maxwiden > 1.15)
L_WARNING("maxwiden = %f > 1.15; suggest between 1.0 and 1.15\n",
__func__, maxwiden);
pixa1 = (debugfile) ? pixaCreate(5) : NULL;
if (pixa1) pixaAddPix(pixa1, pixs, L_COPY);
Expand Down Expand Up @@ -641,51 +643,31 @@ BOX *box1, *box2;

/* Transform to full resolution */
box2 = boxTransform(box1, 0, 0, 2.0, 2.0); /* full res */
if (pixa1) {
pix2 = pixCopy(NULL, pix1);
pixRenderBoxArb(pix2, box2, 5, 255, 0, 0);
pixaAddPix(pixa1, pix2, L_INSERT);
}

/* Adjust sides outward, respecting %lr_clear and %tb_clear */
boxGetSideLocations(box2, &left, &right, &top, &bot);
leftfinal = L_MAX(left - lr_add, lr_clear);
rightfinal = L_MIN(right + lr_add, w - lr_clear);
topfinal = L_MAX(top - tb_add, tb_clear);
botfinal = L_MIN(bot + tb_add, h - tb_clear);
boxSetSideLocations(box2, leftfinal, rightfinal, topfinal, botfinal);
boxDestroy(&box1);
if (pixa1) {
pix2 = pixCopy(NULL, pix1);
pixRenderBoxArb(pix2, box2, 5, 255, 0, 0);
pixaAddPix(pixa1, pix2, L_INSERT);
}

/* Crop the input image */
/* Grab the foreground region */
pix2 = pixClipRectangle(pix1, box2, NULL);
pixDestroy(&pix1);

/* Slightly thicken long horizontal lines. This prevents loss of
* printed thin music staff lines due to aliasing. */
pix3 = pixMorphSequence(pix2, "o80.1 + d1.2", 0);
pixOr(pix2, pix2, pix3);
pixDestroy(&pix3);

/* Widen the result to fit the standard page shape (8.5 x 11 inch).
* Do not stretch horizontally by more than %maxwiden. */
pixGetDimensions(pix2, &w, &h, NULL);
hscale = (l_float32)h / (1.2941f * (l_float32)w);
if (hscale > 1.0 && maxwiden > 1.0) {
hscale = L_MIN(hscale, maxwiden);
pix3 = pixScale(pix2, hscale, 1.0);
if (first_time == TRUE) {
lept_stderr("Widening page by factor %5.3f\n", hscale);
first_time = FALSE;
}
} else {
pix3 = pixClone(pix2);
}
pixDestroy(&pix1);
/* Rescale the fg and paste into the final image */
pix3 = pixRescaleForCropping(pix2, w, h, lr_border, tb_border,
maxwiden, NULL);
pixDestroy(&pix2);
if (pixa1) {
pix2 = pixCopy(NULL, pix3);
pixaAddPix(pixa1, pix2, L_INSERT);
}

if (pcropbox)
*pcropbox = box2;
Expand All @@ -701,6 +683,105 @@ BOX *box1, *box2;
}


/*!
* \brief pixRescaleForCropping()
*
* \param[in] pixs 1 bpp
* \param[in] w width of output lmage
* \param[in] h height of output lmage
* \param[in] lr_border cleared final border pixels on left and right
* \param[in] tb_border cleared final border pixels on top and bottom
* \param[in] maxwiden max fractional horizontal stretch allowed; >= 1.0
* \param[out] *ppixsc [optional] rescaled foreground region
* \return pixd output image, or NULL on error
*
* <pre>
* Notes:
* (1) This rescales %pixs to fit maximally within an image of
* size (w x h), under two conditions:
* (a) the final image has cleared border regions given by the
* input parameters %lr_border and %tb_border, and
* (b) the input image is first isotropically scaled to fit
* maximally within the allowed final region, and then further
* maxiximally widened, subject to the constraints of the
* cleared border and the %maxwiden parameter.
* (2) The cleared border pixel parameters must be >= 0.
* (3) If there is extra horizontal stretching by a factor
* %maxwiden larger than about 1.15, the appearance may be
* unpleasingly distorted; hence the suggestion not to exceed it.
* </pre>
*/
PIX *
pixRescaleForCropping(PIX *pixs,
l_int32 w,
l_int32 h,
l_int32 lr_border,
l_int32 tb_border,
l_float32 maxwiden,
PIX **ppixsc)
{
static l_int32 first_time = TRUE;
l_int32 wi, hi, wmax, hmax, wn, wf, hf, xf;
l_float32 ratio, scaleh, scalew, scalewid;
PIX *pix1, *pixd;

if (ppixsc) *ppixsc = NULL;
if (!pixs || pixGetDepth(pixs) != 1)
return (PIX *)ERROR_PTR("pixs undefined or not 1 bpp", __func__, NULL);
if (lr_border < 0) lr_border = 0;
if (tb_border < 0) tb_border = 0;
maxwiden = L_MAX(1.0, maxwiden);
if (maxwiden > 1.15)
L_WARNING("maxwiden = %f > 1.15; suggest between 1.0 and 1.15\n",
__func__, maxwiden);

/* Rescale the foreground region.
* First, decide if scaling is to full width or full height.
* If scaling to full height, determine how much additional
* width widening is possible, given the maxwiden constraint.
* If scaling to full width, both width and height are
* scaled isotropically. Scaling is done so that the resulting
* foreground is maximally widened, so it can be horizontally
* centered in an image of size (w x h), less %lr_border
* on each side. */
pixGetDimensions(pixs, &wi, &hi, NULL);
wmax = w - 2 * lr_border;
hmax = h - 2 * tb_border;
ratio = (l_float32)(wmax * hi) / (l_float32)(hmax * wi);
if (ratio >= 1) { /* width can be widened after isotropic scaling */
scaleh = (l_float32)hmax / (l_float32)hi;
wn = scaleh * wi; /* scaled but not widened */
scalewid = L_MIN(maxwiden, (l_float32)wmax / (l_float32)wn);
scalew = scaleh * scalewid;
wf = scalew * wi;
hf = hmax; /* scale to full height */
pix1 = pixScale(pixs, scalew, scaleh);
if (first_time == TRUE) {
lept_stderr("Width stretched by factor %5.3f\n", scalewid);
first_time = FALSE;
}
xf = (w - wf) / 2.0;
} else { /* width cannot be widened after isotropic scaling */
scalew = (l_float32)wmax / (l_float32)wi;
pix1 = pixScale(pixs, scalew, scalew);
wf = wmax; /* scale to full width */
hf = scalew * hi; /* no extra vertical stretching allowed */
xf = lr_border;
}

/* Paste it, horizontally centered and vertically placed as
* high as allowed (by %tb_border) into the final page image. */
pixd = pixCreate(w, h, 1);
pixRasterop(pixd, xf, tb_border, wf, hf, PIX_SRC, pix1, 0, 0);

if (ppixsc)
*ppixsc = pix1;
else
pixDestroy(&pix1);
return pixd;
}


/*!
* \brief pixCleanImage()
*
Expand Down

0 comments on commit 46784f4

Please sign in to comment.