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.
- Native Transfer
- The DS returns a DIB image in memory.
- The application, such as XnView, may save the final file itself.
-
DPI must be propagated through TW_IMAGEINFO and DIB metadata.
-
File Transfer
- The DS writes the final file.
- 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:
- Open XnView.
- Choose Scan to.
- Select output format.
- Click Scan.
- In settings UI, choose DPI, Page Size, and Page Fill.
- Click Scan.
- Verify pixel width and height match page size times DPI.
- Verify Stretch distorts the image to the page aspect ratio.
- Verify Fit with padding preserves aspect ratio and uses white padding.
- Verify Fill and crop preserves aspect ratio and center-crops overflow.
- 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.