Skip to content

Page Size and Page Fill Support Design

Design notes for adding selectable page-size output and page-fill behavior to the BN Tech Virtual Scanner settings UI.

1. Requirement

The settings UI adds a Page Size dropdown. Options are US Letter, US Legal, A4, and A5.

The UI also adds a Page Fill dropdown directly below Page Size. Options are Stretch, Fit with padding, and Fill and crop. The default is Stretch.

After the user selects page size, DPI, and page-fill behavior, final scan output pixel size is computed as:

pixel_width  = page_width_in_inches  * horizontal_dpi
pixel_height = page_height_in_inches * vertical_dpi

Example:

US Letter = 8.5 x 11 inches
DPI       = 600
Output    = 5100 x 6600 pixels

The output must also keep matching DPI metadata, so Windows Explorer and image applications show the selected horizontal and vertical resolution.

The page-fill setting only controls how source-image content is mapped into the target page canvas. It does not change the final output pixel size.

2. Domain knowledge

2.1 TWAIN transfer paths

The TWAIN Data Source supports two relevant output paths.

  1. Native Transfer
  2. The DS returns a DIB image in memory.
  3. The application, such as XnView, may save the final file itself.
  4. DPI must be propagated through TW_IMAGEINFO and DIB metadata.

  5. File Transfer

  6. The DS writes the final file.
  7. The DS can patch file format specific DPI metadata after saving.

Page size must be applied before either transfer path returns image data.

2.2 DPI meaning

DPI means pixels per inch. TWAIN resolution values use:

ICAP_XRESOLUTION
ICAP_YRESOLUTION
ICAP_UNITS = TWUN_INCHES

ICAP_UNITS = TWUN_INCHES tells TWAIN applications that the resolution values are pixels per inch.

2.3 Page sizes

Page size Physical size Inches used
US Letter 8.5 x 11 in 8.5 x 11.0
US Legal 8.5 x 14 in 8.5 x 14.0
A4 210 x 297 mm 8.2677 x 11.6929
A5 148 x 210 mm 5.8268 x 8.2677

A-series sizes are converted to inches and rounded to four decimal places.

2.4 DPI metadata by file type

Format Metadata field Internal unit
PNG pHYs chunk pixels per meter
JPG JFIF APP0 density dots per inch
BMP biXPelsPerMeter and biYPelsPerMeter pixels per meter
TIFF XResolution, YResolution, ResolutionUnit rational plus inch unit

PNG and BMP use pixels per meter internally, so the scanner converts DPI to pixels per meter.

2.5 Page-fill behavior

Page-fill behavior describes how to handle aspect-ratio differences between the source image and the target page.

Fill mode Preserves source aspect ratio Covers final page May add padding May crop
Stretch No Yes No No
Fit with padding Yes Yes Yes No
Fill and crop Yes Yes No Yes
  • Stretch: resize the source image directly to the target page size. This may distort the source aspect ratio.
  • Fit with padding: use the smaller scale factor so the full source image fits inside the page, then fill the remaining canvas with white padding.
  • Fill and crop: use the larger scale factor so the page is fully covered, then crop overflow around the centered page area.

Page-fill behavior is an image-content mapping strategy. It is not a TWAIN resolution or file DPI metadata strategy. For all fill modes, the final bitmap width and height still equal page size times DPI.

3. Design goals

  • Keep code changes small.
  • Reuse the existing settings UI.
  • Reuse ScannerSettings to pass page size and page-fill behavior.
  • Reuse FreeImage_Rescale for resizing.
  • Use FreeImage_Allocate, FreeImage_FillBackground, and FreeImage_Paste for padded canvases.
  • Use FreeImage_Copy for centered crop.
  • Keep existing DPI metadata fixes unchanged.
  • Do not add a new TWAIN page-size capability yet.

Non-goals:

  • No orientation selector.
  • No manual crop rectangle UI.
  • No background color selector; Fit with padding always uses white.
  • No ICAP_SUPPORTEDSIZES support yet.
  • No persistence of page size yet.

4. Workflow

Application requests scan
  -> DS opens settings UI when ShowUI is true
  -> User selects DPI, Page Size, and Page Fill
  -> UI submits values to DS
  -> DS copies values into ScannerSettings
  -> VirtualScanner loads next source image
  -> VirtualScanner computes target canvas from page size times DPI
  -> VirtualScanner scales, pads, or crops according to Page Fill
  -> VirtualScanner applies pixel type conversion
  -> VirtualScanner applies DPI metadata
  -> DS returns Native Transfer image or saves File Transfer output

5. Algorithm

5.1 Page size index

The selected page size is stored as an integer.

0 = US Letter
1 = US Legal
2 = A4
3 = A5

Invalid values fall back to US Letter.

5.2 Page size lookup

The scanner maps the index to width and height in inches.

0 -> 8.5,    11.0
1 -> 8.5,    14.0
2 -> 8.2677, 11.6929
3 -> 5.8268, 8.2677

5.3 Pixel calculation

Given page_width_in, page_height_in, x_dpi, and y_dpi:

target_width  = round(page_width_in  * x_dpi)
target_height = round(page_height_in * y_dpi)

The implementation uses add 0.5 then cast to int for rounding.

5.4 Page-fill mode index

The selected page-fill behavior is stored as an integer.

0 = Stretch
1 = Fit with padding
2 = Fill and crop

Invalid values fall back to Stretch.

5.5 Scaling and fill algorithm

All modes first compute the target page pixel size: target_width and target_height. The fill mode only controls how source pixels map into that target page.

5.5.1 Stretch

Stretch directly resizes the source image to the target page dimensions:

output_width  = target_width
output_height = target_height

It uses FreeImage_Rescale with FILTER_BILINEAR. This mode adds no padding and crops nothing, but it may distort the source aspect ratio.

5.5.2 Fit with padding

Fit with padding preserves source aspect ratio and keeps the full source image visible.

Given source dimensions src_width and src_height:

scale_x = target_width  / src_width
scale_y = target_height / src_height
scale   = min(scale_x, scale_y)

Scaled content size:

scaled_width  = round(src_width  * scale)
scaled_height = round(src_height * scale)

Then create a white target-page canvas:

canvas_width  = target_width
canvas_height = target_height

Paste the scaled source image centered on the canvas:

left = (target_width  - scaled_width)  / 2
top  = (target_height - scaled_height) / 2

Implementation uses:

  • FreeImage_Rescale to scale the source image.
  • FreeImage_Allocate to create the target canvas.
  • FreeImage_FillBackground to fill the canvas with white.
  • FreeImage_Paste to paste the scaled image centered in the canvas.

5.5.3 Fill and crop

Fill and crop preserves source aspect ratio and ensures the target page is fully covered.

scale_x = target_width  / src_width
scale_y = target_height / src_height
scale   = max(scale_x, scale_y)

Scaled content size:

scaled_width  = ceil(src_width  * scale)
scaled_height = ceil(src_height * scale)

Then crop the target page area from the center of the scaled image:

left = (scaled_width  - target_width)  / 2
top  = (scaled_height - target_height) / 2
crop = [left, top, left + target_width, top + target_height]

Implementation uses:

  • FreeImage_Rescale to scale the source image.
  • FreeImage_Copy to crop the centered target page area.

5.6 Metadata

After scaling, applyDpiMetadata writes DPI to the in-memory bitmap. For file transfer, patchSavedDpiMetadata still patches PNG, JPG, BMP, and TIFF container metadata after saving.

6. Examples

6.1 Page pixel-size examples

Page size 300 DPI 600 DPI
US Letter 2550 x 3300 5100 x 6600
US Legal 2550 x 4200 5100 x 8400
A4 2480 x 3508 4961 x 7016
A5 1748 x 2480 3496 x 4961

6.2 Page-fill examples

Assume the source image is 1600 x 900 and the target page is US Letter at 300 DPI, so the target is 2550 x 3300.

Fill mode Scaled content size Final output size Result
Stretch 2550 x 3300 2550 x 3300 Image is distorted to page aspect ratio
Fit with padding 2550 x 1434 2550 x 3300 Top/bottom padding, full source visible
Fill and crop 5867 x 3300 2550 x 3300 Left/right crop, page fully covered

7. Layer changes

7.1 Settings UI layer

Files:

src/settings_server.h
src/settings_server.cpp

Changes:

  • Add page_size and page_fill_mode to SettingsUiResult.
  • Add Page Size dropdown to generated HTML.
  • Add Page Fill dropdown below Page Size.
  • Submit pagesize and pagefillmode with the scan request.
  • Parse pagesize and pagefillmode in parseFormData.

7.2 TWAIN data source layer

File:

src/twain_data_source.cpp

Changes:

  • Initialize ui_result.page_size from scanner_.getSettings().page_size.
  • Initialize ui_result.page_fill_mode from scanner_.getSettings().page_fill_mode.
  • Copy ui_result.page_size and ui_result.page_fill_mode into ScannerSettings after the user clicks Scan.
  • Existing DPI propagation remains unchanged.

7.3 Scanner settings layer

File:

src/virtual_scanner.h

Change:

ScannerSettings now includes page_size and page_fill_mode.

Default page size is US Letter. Default page-fill mode is Stretch.

7.4 Image processing layer

File:

src/virtual_scanner.cpp

Changes:

  • resetScanner sets settings_.page_size to 0.
  • resetScanner sets settings_.page_fill_mode to 0.
  • preScanPrep calls applyPageSizeScaling.
  • applyPageSizeScaling computes output pixels from page size and DPI.
  • applyPageSizeScaling applies Stretch, Fit with padding, or Fill and crop according to the selected page-fill mode.
  • FreeImage_Rescale performs the actual resize.
  • FreeImage_Allocate, FreeImage_FillBackground, and FreeImage_Paste perform padded-canvas handling.
  • FreeImage_Copy performs centered cropping.

Processing order:

Load image
  -> Ensure 24-bit DIB
  -> Compute target page from selected page size and DPI
  -> Scale, pad, or crop according to selected page-fill mode
  -> Convert pixel type
  -> Apply DPI metadata
  -> Calculate row parameters

7.5 Metadata layer

No major new metadata algorithm is needed. Existing logic still handles:

  • TW_IMAGEINFO XResolution and YResolution.
  • PNG pHYs.
  • JPG JFIF density.
  • BMP DIB resolution fields.
  • TIFF resolution tags.

8. End-to-end data flow

TWAIN application
  -> TwainDataSource MSG_ENABLEDS
  -> SettingsServer showSettingsUi
  -> user selects DPI, page size, and page-fill mode
  -> TwainDataSource stores values in ScannerSettings
  -> VirtualScanner acquireImage
  -> FreeImage loads source image
  -> VirtualScanner computes target page from page inches times DPI
  -> VirtualScanner scales, pads, or crops according to fill mode
  -> VirtualScanner applies pixel type and DPI metadata
  -> TwainDataSource sends MSG_XFERREADY
  -> Application requests image info and image data
  -> Application receives page-sized image

9. Testing checklist

Native transfer, for example XnView Scan to:

  1. Open XnView.
  2. Choose Scan to.
  3. Select output format.
  4. Click Scan.
  5. In settings UI, choose DPI, Page Size, and Page Fill.
  6. Click Scan.
  7. Verify pixel width and height match page size times DPI.
  8. Verify Stretch distorts the image to the page aspect ratio.
  9. Verify Fit with padding preserves aspect ratio and uses white padding.
  10. Verify Fill and crop preserves aspect ratio and center-crops overflow.
  11. Verify Windows Details tab shows selected DPI.

File transfer:

  • Test PNG, JPG, BMP, and TIFF.
  • Verify pixel dimensions.
  • Verify DPI metadata.

10. Limitations and future improvements

Current limitations:

  • Fit with padding uses a fixed white background; the UI cannot change it.
  • Fill and crop always uses centered cropping; the UI cannot select a crop anchor.
  • Page orientation is portrait only.
  • Page size is not exposed via ICAP_SUPPORTEDSIZES.
  • Page size is not persisted across sessions.

Future improvements:

  • Add portrait and landscape orientation.
  • Allow users to choose Fit with padding background color.
  • Allow users to choose Fill and crop anchor point.
  • Add ICAP_SUPPORTEDSIZES and ICAP_FRAMES support.
  • Persist last selected page size and fill mode.
  • Add automated tests for dimensions and DPI metadata.