Guest User

Page/Panel imaging/compositing library

a guest
Jun 7th, 2026
41
0
Never
1
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Bash 42.13 KB | Source Code | 0 0
  1. #!/opt/local/bin/bash5
  2.  
  3. #===============================================================================
  4. # PAGE/PANEL IMAGING/COMPOSITING LIBRARY
  5. #
  6. # This is a library of functions used to manipulate PNM images, the main goal
  7. # of which being to assemble panel images into a final page.
  8. #
  9. # This file must be sourced by scripts which create individual pages.
  10. #
  11. # The code here provides a complete page rendering system that composites
  12. # multiple image panels onto a configurable background.  Panels can include
  13. # optional borders, transparency cutouts, and gutters to create dynamic
  14. # layouts.  It supports reading various image formats including JPEG, JPEG-XL,
  15. # PNG, and NetPBM (PBM/PGM/PPM/PAM) files, and converts them into a uniform RGB
  16. # or RGBA stream for processing.
  17. #
  18. # Panel data is stored in a global associative array with position, size, and
  19. # image path.  The pipeline automatically selects RGB or RGBA compositing based
  20. # on panel requirements, enabling support for transparent cutouts.  Pages can
  21. # have solid, gradient, or image backgrounds, with images automatically scaled
  22. # or cropped to fit.
  23. #
  24. # The library provides functions to overlay panels, rectangles, labels,
  25. # watermarks, and page-edge shadows.  Labels and watermarks are rendered from
  26. # fonts into masks and composited with specified alignment and opacity.
  27. # Shadows are dynamically generated according to background brightness,
  28. # simulating book folds on verso and recto pages.
  29. #
  30. # Page rendering is implemented as a streaming pipeline:  background creation,
  31. # panel overlay, label placement, watermark application, shadow rendering, and
  32. # final output to disk.  Recursive functions are used to sequentially overlay
  33. # panels.  An alternative entry point allows adding overlays to a previously
  34. # rendered intermediate image.  All parameters are validated prior to rendering
  35. # to ensure consistency and correctness.
  36.  
  37.  
  38. #===============================================================================
  39. # GLOBAL VARIABLES
  40. #
  41. # Externally defined constants (defined prior to sourcing this module):
  42. #
  43. #   CONTENT_HEIGHT
  44. #   CONTENT_WIDTH
  45. #     Height and width of main content on page, in pixels, not counting panel
  46. #     borders.
  47. #
  48. #   MARGIN_BOTTOM
  49. #   MARGIN_TOP
  50. #   MARGIN_LEFT
  51. #   MARGIN_RIGHT
  52. #     Size of margin areas, in pixels.
  53. #
  54. #   PANEL_BORDER
  55. #     Thickness of border around panel images, in pixels.
  56. #
  57. #   PANEL_BORDER_COLOR
  58. #     Color of panel border, given as a hexadecimal color value.
  59. #
  60. #   PANEL_GUTTER
  61. #     Thickness of gutter between panel images, in pixels, not counting borders.
  62. #
  63. #   PAGE_HEIGHT
  64. #   PAGE_WIDTH
  65. #     Height and width of full page, in pixels.  This determines exactly the
  66. #     dimensions of the final output image.
  67. #
  68. #   PAGE_SHADOW_GAMMA
  69. #     Gamma curve adjustment value determining the light falloff of rendered
  70. #     shadows on the page.  (Used only in verso/recto page configurations.)
  71. #
  72. #   PAGE_SHADOW_WIDTH
  73. #     Width of shadow in pixels, counting from the center of the page spread.
  74. #     (Used only in verso/recto page configurations.)
  75. #
  76. #   PAGE_LABEL
  77. #     Text to be displayed beneath the panels, either centered or flush left or
  78. #     flush right.  Can be the empty string, but is typically a page number.
  79. #
  80. #   PAGE_LABEL_COLOR
  81. #     Color of page label text, given as a hexadecimal color value.
  82. #
  83. #   PAGE_LABEL_GUTTER
  84. #     Height, in pixels, of the gap between the page content and the page label.
  85. #
  86. #   PAGE_LABEL_HEIGHT
  87. #     Height, in pixels, of the area allocated for the page label.
  88. #
  89. #   PAGE_LABEL_FONT_SIZE
  90. #     Height of rendered page label, in pixels (DOUBLE-CHECK THIS!), spanning
  91. #     the entire em height of the typeface (ALSO DOUBLE-CHECK THIS!).
  92. #
  93. #   PAGE_LABEL_FONT
  94. #     PostScript-compatible typeface name to be used in rendering the page
  95. #     label.
  96. #
  97. #   TEXT_WATERMARK
  98. #     Text to be displayed vertically along the sides of pages to indicate a
  99. #     draft or special copy for certain eyes only.  (Normally left blank.)
  100. #     The color is assumed to be the same as PANEL_BORDER, but at 25% opacity.
  101. #
  102. #   TEXT_WATERMARK_GUTTER
  103. #     Width, in pixels, of the gap between the page content and the text
  104. #     watermark (which is rendered vertically).
  105. #
  106. #   TEXT_WATERMARK_HEIGHT
  107. #     Width, in pixels, of the area allocated for the text watermark (which is
  108. #     rendered vertically).
  109. #
  110. #   TEXT_WATERMARK_FONT_SIZE
  111. #     Size of the rendered text watermark, in pixels (DOUBLE-CHECK THIS!).
  112. #
  113. #   TEXT_WATERMARK_FONT
  114. #     PostScript-compatible typeface name to be used in rendering the text
  115. #     watermark.
  116. #
  117. #   SPLASH_WATERMARK
  118. #     Text to be displayed diagonally across the whole page, very obnoxiously.
  119. #     (Normally left blank.)  The color is assumed to be white, but at 15%
  120. #     opacity.
  121. #
  122. #   SPLASH_WATERMARK_FONT_SIZE
  123. #     Size of the rendered full-page splash watermark, in pixels (DOUBLE-CHECK
  124. #     THIS!).
  125. #
  126. #   SPLASH_WATERMARK_FONT
  127. #     PostScript-compatible typeface name to be used in rendering the full-page
  128. #     splash watermark.
  129. #
  130. #   FONTS_DIR  [currently unused]
  131. #     Directory containing TTF files to use in rendering text.
  132. #
  133. # Interally defined:
  134. #
  135. #   BACKGROUND_COLOR1
  136. #   BACKGROUND_COLOR2
  137. #     Defaults to empty string.  Optionally contains a hexadecimal color code.
  138. #     If no colors are defined, a solid white backdrop is applied.  If one
  139. #     color is defined, a solid backdrop of that color is applied.  If two
  140. #     colors are defined, a linear gradient is applied vertically, with the
  141. #     first color at the top and the second color at the bottom.
  142. #
  143. #   BACKGROUND_IMAGE_PATH
  144. #     Defaults to empty string.  Optionally contains path to image file.
  145. #
  146. #   PANEL_LIST
  147. #     Array of panels on the page.  Starts empty and is grown as panels are
  148. #     added.
  149. #
  150. #   PANEL_COUNT
  151. #     Count of panels on the page.  Starts at zero and counts upward as panels
  152. #     are added.
  153. #
  154. #   PANEL_FIELDS
  155. #     Constant array of field names for panel attributes.
  156. #
  157. #   PANEL_RENDERING_PIPELINE
  158. #     String determining which rendering pipeline (RGB or RGBA) is to be used
  159. #     when compositing the page.  Starts as "rgb" and may change to "rgba" if
  160. #     a more complex rendering pipeline is needed, for example overlapping
  161. #     panels.
  162.  
  163. #-------------------------------------------------------------------------------
  164.  
  165.  
  166. #===============================================================================
  167. # ERROR HANDLING
  168.  
  169. #-------------------------------------------------------------------------------
  170. error_message()
  171. {
  172.   printf "%s: %s\n" $0 "$*" 1>&2
  173.  
  174.   false
  175. }
  176.  
  177. #-------------------------------------------------------------------------------
  178. error_message_exit()
  179. {
  180.   error_message "$@"
  181.  
  182.   case $- in
  183.     *i*) false ;;    # Interactive shell context
  184.     *)   exit 1 ;;   # Non-interactive script context
  185.   esac
  186. }
  187.  
  188. #-------------------------------------------------------------------------------
  189. suppress_stderr()
  190. {
  191.   local SUPPRESS="$1"; shift
  192.  
  193.   "$@" 2> >(grep -v "$SUPPRESS" 1>&2)
  194. }
  195.  
  196.  
  197. #===============================================================================
  198. # PARAMETER TESTS
  199.  
  200. #-------------------------------------------------------------------------------
  201. param_general_validate()
  202. {
  203.   local PARAM_NAME="$1"
  204.   local PARAM_VALUE="$2"
  205.   local REGEX_MATCH="$3"
  206.   local TYPE="$4"
  207.  
  208.   if [[ ! "$PARAM_VALUE" =~ $REGEX_MATCH ]]; then
  209.     error_message "${PARAM_NAME}: ${PARAM_VALUE}: Invalid value; must be $TYPE"
  210.     return 1
  211.   fi
  212.  
  213.   return 0
  214. }
  215.  
  216. #-------------------------------------------------------------------------------
  217. param_integer_validate()
  218. {
  219.   local PARAM_NAME="$1"
  220.   local PARAM_VALUE="$2"
  221.  
  222.   param_general_validate \
  223.     "$PARAM_NAME" "$PARAM_VALUE" '^-?(0|[1-9][0-9]*)$' "integer"
  224. }
  225.  
  226. #-------------------------------------------------------------------------------
  227. param_nonnegative_integer_validate()
  228. {
  229.   local PARAM_NAME="$1"
  230.   local PARAM_VALUE="$2"
  231.  
  232.   param_general_validate \
  233.     "$PARAM_NAME" "$PARAM_VALUE" '^(0|[1-9][0-9]*)$' "non-negative integer"
  234. }
  235.  
  236. #-------------------------------------------------------------------------------
  237. param_positive_integer_validate()
  238. {
  239.   local PARAM_NAME="$1"
  240.   local PARAM_VALUE="$2"
  241.  
  242.   param_general_validate \
  243.     "$PARAM_NAME" "$PARAM_VALUE" '^[1-9][0-9]*$' "positive integer"
  244. }
  245.  
  246. #-------------------------------------------------------------------------------
  247. param_real_validate()
  248. {
  249.   local PARAM_NAME="$1"
  250.   local PARAM_VALUE="$2"
  251.  
  252.   param_general_validate \
  253.     "$PARAM_NAME" "$PARAM_VALUE" \
  254.     '^-?([0-9]+|[0-9]*\.[0-9]+|[0-9]+\.[0-9]*)$' \
  255.     "real number"
  256. }
  257.  
  258.  
  259. #===============================================================================
  260. # FILENAME TESTS
  261.  
  262. #-------------------------------------------------------------------------------
  263. image_path_validate()
  264. {
  265.   local IMAGE_PATH="$1"
  266.  
  267.   if [[ ! -e "$IMAGE_PATH" ]]; then
  268.     error_message "${IMAGE_PATH}: Not found"
  269.     return 1
  270.   fi
  271.  
  272.   case "${IMAGE_PATH##*.}" in
  273.     jpg|jpeg)
  274.       ;;
  275.     jxl|jpegxl)
  276.       ;;
  277.     png)
  278.       ;;
  279.     pbm|pgm|ppm|pnm|pam)
  280.       ;;
  281.     *)
  282.       error_message_exit "${IMAGE_PATH}: Unsupported image file extension"
  283.       return 1
  284.       ;;
  285.   esac
  286.  
  287.   return 0
  288. }
  289.  
  290. #-------------------------------------------------------------------------------
  291. image_filename_has_borderless_directive()
  292. {
  293.   local IMAGE_PATH="$1"
  294.  
  295.   local REGEX_MATCH_BORDERLESS_DIRECTIVE='\[borderless\]'
  296.  
  297.   [[ "${IMAGE_PATH##*/}" =~ $REGEX_MATCH_BORDERLESS_DIRECTIVE ]]
  298. }
  299.  
  300. #-------------------------------------------------------------------------------
  301. image_filename_has_cutout_directive()
  302. {
  303.   local IMAGE_PATH="$1"
  304.  
  305.   local REGEX_MATCH_CUTOUT_DIRECTIVE='\^'
  306.  
  307.   [[ "${IMAGE_PATH##*/}" =~ $REGEX_MATCH_CUTOUT_DIRECTIVE ]]
  308. }
  309.  
  310.  
  311. #===============================================================================
  312. # IMAGE CREATION (ARBITRARY SIZE)
  313.  
  314. #-------------------------------------------------------------------------------
  315. image_rgb_make_color()
  316. {
  317.   local WIDTH=$1
  318.   local HEIGHT=$2
  319.   local COLOR="$3"
  320.  
  321.   ppmmake -maxval=255 "$COLOR" $WIDTH $HEIGHT
  322. }
  323.  
  324. #-------------------------------------------------------------------------------
  325. image_rgb_make_color_gradient()
  326. {
  327.   local WIDTH=$1
  328.   local HEIGHT=$2
  329.   local COLOR_TOP="$3"
  330.   local COLOR_BOTTOM="$4"
  331.  
  332.   pamgradient -maxval=255 \
  333.     "$COLOR_TOP" "$COLOR_TOP" "$COLOR_BOTTOM" "$COLOR_BOTTOM" \
  334.     $WIDTH $HEIGHT
  335. }
  336.  
  337. #-------------------------------------------------------------------------------
  338. image_rgba_make_color()
  339. {
  340.   local WIDTH=$1
  341.   local HEIGHT=$2
  342.   local COLOR="$3"
  343.   local OPACITY=$4
  344.  
  345.   pamstack \
  346.     -quiet -tupletype RGB_ALPHA \
  347.     <( ppmmake -maxval=255 "$COLOR" $WIDTH $HEIGHT ) \
  348.     <( pgmmake -maxval=255 $OPACITY $WIDTH $HEIGHT )
  349. }
  350.  
  351. #-------------------------------------------------------------------------------
  352. image_rgba_make_transparent()
  353. {
  354.   local WIDTH=$1
  355.   local HEIGHT=$2
  356.  
  357.   image_rgba_make_color $WIDTH $HEIGHT "black" 0
  358. }
  359.  
  360. #-------------------------------------------------------------------------------
  361. image_rgba_make_opaque_black()
  362. {
  363.   local WIDTH=$1
  364.   local HEIGHT=$2
  365.  
  366.   image_rgba_make_color $WIDTH $HEIGHT "black" 1
  367. }
  368.  
  369. #-------------------------------------------------------------------------------
  370. image_rgba_make_opaque_white()
  371. {
  372.   local WIDTH=$1
  373.   local HEIGHT=$2
  374.  
  375.   image_rgba_make_color $WIDTH $HEIGHT "white" 1
  376. }
  377.  
  378.  
  379. #===============================================================================
  380. # IMAGE OUTPUT
  381. #
  382. # Write stream to final output format on disk.
  383.  
  384. #-------------------------------------------------------------------------------
  385. image_write()
  386. {
  387.   local IMAGE_PATH="$1"
  388.  
  389.   if [[ "$IMAGE_PATH" == "" ]] || [[ "$IMAGE_PATH" == "-" ]]; then
  390.     cat
  391.   else
  392.     case "${IMAGE_PATH##*.}" in
  393.       jpg|jpeg)
  394.         pnmtojpeg -dct=float -optimize -quality 100 >"$IMAGE_PATH"
  395.         ;;
  396.       jxl|jpegxl)
  397.         ppmtojxl -q 100 -e 3 >"$IMAGE_PATH"
  398.         ;;
  399.       png)
  400.         pamtopng >"$IMAGE_PATH"
  401.         ;;
  402.       ppm)
  403.         ppmtoppm >"$IMAGE_PATH"
  404.         ;;
  405.       pnm)
  406.         pnmtopnm >"$IMAGE_PATH"
  407.         ;;
  408.       pam)
  409.         pamtopam >"$IMAGE_PATH"
  410.         ;;
  411.       *)
  412.         error_message_exit "${IMAGE_PATH}: Unsupported image file extension"
  413.         return
  414.         ;;
  415.     esac
  416.   fi
  417. }
  418.  
  419.  
  420. #===============================================================================
  421. # IMAGE INPUT
  422. #
  423. # Read image files from disk and produce input stream.
  424.  
  425. #-------------------------------------------------------------------------------
  426. image_dimensions()
  427. {
  428.   local IMAGE_PATH="$1"
  429.  
  430.   image_rgb_borderless "$IMAGE_PATH" | head -2 | tail -1
  431. }
  432.  
  433. #-------------------------------------------------------------------------------
  434. image_rgb_borderless()
  435. {
  436.   local IMAGE_PATH="$1"
  437.   local WIDTH=$2   # (unused)
  438.   local HEIGHT=$3  # (unused)
  439.  
  440.   case "${IMAGE_PATH##*.}" in
  441.     jpg|jpeg)
  442.       jpegtopnm -quiet -dct float "$IMAGE_PATH"
  443.       ;;
  444.     jxl|jpegxl)
  445.       jxltoppm "$IMAGE_PATH"
  446.       ;;
  447.     png)
  448.       pngtopam "$IMAGE_PATH"
  449.       ;;
  450.     pbm|pgm|ppm|pnm|pam)
  451.       cat "$IMAGE_PATH"
  452.       ;;
  453.     *)
  454.       error_message_exit "${IMAGE_PATH}: Unsupported image file extension"
  455.       return
  456.       ;;
  457.   esac
  458. }
  459.  
  460. #-------------------------------------------------------------------------------
  461. image_rgb_bordered()
  462. {
  463.   local IMAGE_PATH="$1"
  464.   local WIDTH=$2
  465.   local HEIGHT=$3
  466.  
  467.   local PNMPAD_COLOR=
  468.   case "$PANEL_BORDER_COLOR" in
  469.     "#000000"|"#000"|"#0"|black)  PNMPAD_COLOR=black ;;
  470.     "#FFFFFF"|"#FFF"|"#F"|white)  PNMPAD_COLOR=white ;;
  471.     *)                            PNMPAD_COLOR=      ;;
  472.   esac
  473.  
  474.   if [[ "$PNMPAD_COLOR" != "" ]]; then
  475.     pnmpad \
  476.       -$PNMPAD_COLOR \
  477.       -left=$PANEL_BORDER -right=$PANEL_BORDER \
  478.       -top=$PANEL_BORDER -bottom=$PANEL_BORDER \
  479.       <( image_rgb_borderless "$IMAGE_PATH" $WIDTH $HEIGHT )
  480.   else
  481.     pamcomp \
  482.       -align=center -valign=middle \
  483.       <( image_rgb_borderless "$IMAGE_PATH" $WIDTH $HEIGHT ) \
  484.       <( ppmmake \
  485.            -maxval 255 \
  486.            "$PANEL_BORDER_COLOR" \
  487.            $(( WIDTH  + (PANEL_BORDER * 2) )) \
  488.            $(( HEIGHT + (PANEL_BORDER * 2) )) \
  489.        )
  490.   fi
  491. }
  492.  
  493. #-------------------------------------------------------------------------------
  494. image_rgba_bordered()
  495. {
  496.   local IMAGE_PATH="$1"
  497.   local WIDTH=$2
  498.   local HEIGHT=$3
  499.  
  500.   pamstack \
  501.     -quiet -tupletype RGB_ALPHA \
  502.     <( image_rgb_bordered "$IMAGE_PATH" \
  503.          $WIDTH $HEIGHT ) \
  504.     <( pgmmake -maxval=255 1 \
  505.          $(( WIDTH  + (PANEL_BORDER * 2) )) \
  506.          $(( HEIGHT + (PANEL_BORDER * 2) )) \
  507.     )
  508. }
  509.  
  510. #-------------------------------------------------------------------------------
  511. image_rgb()
  512. {
  513.   local IMAGE_PATH="$1"
  514.   local WIDTH=$2
  515.   local HEIGHT=$3
  516.  
  517.   if image_filename_has_borderless_directive "$IMAGE_PATH"; then
  518.     image_rgb_borderless "$IMAGE_PATH" $WIDTH $HEIGHT
  519.   else
  520.     image_rgb_bordered   "$IMAGE_PATH" $WIDTH $HEIGHT
  521.   fi
  522. }
  523.  
  524.  
  525. #===============================================================================
  526. # MAINTAIN PANEL LIST
  527.  
  528. #-------------------------------------------------------------------------------
  529. BACKGROUND_IMAGE_PATH=
  530. BACKGROUND_COLOR1=
  531. BACKGROUND_COLOR2=
  532.  
  533. declare -a PANEL_FIELDS=( \
  534.   LEFT \
  535.   TOP \
  536.   WIDTH \
  537.   HEIGHT \
  538.   IMAGE_PATH \
  539. )
  540. declare -A PANEL_LIST=
  541. declare -i PANEL_COUNT=
  542.  
  543. PANEL_RENDERING_PIPELINE=
  544.  
  545. #-------------------------------------------------------------------------------
  546. panel_list_init()
  547. {
  548.   BACKGROUND_IMAGE_PATH=
  549.   BACKGROUND_COLOR1=white
  550.   BACKGROUND_COLOR2=
  551.   PANEL_LIST=()
  552.   PANEL_COUNT=0
  553.   PANEL_RENDERING_PIPELINE=rgb
  554. }
  555.  
  556. #-------------------------------------------------------------------------------
  557. panel_list_set_panel_border_color()
  558. {
  559.   PANEL_BORDER_COLOR="$1"
  560. }
  561.  
  562. #-------------------------------------------------------------------------------
  563. panel_list_set_page_label_color()
  564. {
  565.   PAGE_LABEL_COLOR="$1"
  566. }
  567.  
  568. #-------------------------------------------------------------------------------
  569. panel_list_set_background_color()
  570. {
  571.   BACKGROUND_COLOR1="$1"
  572.   BACKGROUND_COLOR2="$2"
  573. }
  574.  
  575. #-------------------------------------------------------------------------------
  576. panel_list_set_background_image()
  577. {
  578.   BACKGROUND_IMAGE_PATH="$1"
  579. }
  580.  
  581. #-------------------------------------------------------------------------------
  582. panel_list_set_background()
  583. {
  584.   local BACKGROUND1="$1"
  585.   local BACKGROUND2="$2"
  586.  
  587.   case "$BACKGROUND1" in
  588.     "")
  589.       ;;
  590.     \#*)
  591.       panel_list_set_background_color "$BACKGROUND1" "$BACKGROUND2"
  592.       ;;
  593.     *.jpg|*.jpeg|*.jxl|*.jpegxl|*.png|*.pbm|*.pgm|*.ppm|*.pnm|*.pam)
  594.       panel_list_set_background_image "$BACKGROUND1"
  595.       ;;
  596.     *)
  597.       error_message_exit "${BACKGROUND1}: Unsupported background type"
  598.       ;;
  599.   esac
  600. }
  601.  
  602. #-------------------------------------------------------------------------------
  603. panel_list_append()
  604. {
  605.   local LEFT=$1
  606.   local TOP=$2
  607.   local WIDTH=$3
  608.   local HEIGHT=$4
  609.   local IMAGE_PATH="$5"
  610.  
  611.   if image_filename_has_cutout_directive "$IMAGE_PATH"; then
  612.     PANEL_RENDERING_PIPELINE=rgba
  613.   fi
  614.  
  615.   local FIELD
  616.   for FIELD in "${PANEL_FIELDS[@]}"; do
  617.     eval "PANEL_LIST[$PANEL_COUNT,$FIELD]"='$'"$FIELD"
  618.   done
  619.  
  620.   PANEL_COUNT+=1
  621. }
  622.  
  623. #-------------------------------------------------------------------------------
  624. # Fetch a single panel from the panel list
  625. #
  626. # ENTRY:  $1 specifies a panel by index.
  627. #         PANEL is an associative array in the dynamic scope.
  628. #
  629. # EXIT:   PANEL is populated with data, replacing any previous data.
  630.  
  631. panel_list_fetch_by_index()
  632. {
  633.   local PANEL_INDEX=$1
  634.  
  635.   if (( PANEL_INDEX < 0 )) || (( PANEL_INDEX >= PANEL_COUNT )); then
  636.     error_message_exit "Invalid panel index $PANEL_INDEX"
  637.     return
  638.   fi
  639.  
  640.   PANEL=()
  641.   local FIELD
  642.   for FIELD in "${PANEL_FIELDS[@]}"; do
  643.     PANEL[$FIELD]="${PANEL_LIST[$PANEL_INDEX,$FIELD]}"
  644.   done
  645. }
  646.  
  647. #-------------------------------------------------------------------------------
  648. panel_list_dump_info_by_index()
  649. {
  650.   local PANEL_INDEX=$1
  651.  
  652.   local -A PANEL
  653.   panel_list_fetch_by_index $PANEL_INDEX
  654.  
  655.   local FIELD
  656.   for FIELD in "${PANEL_FIELDS[@]}"; do
  657.     echo "Panel $PANEL_INDEX $FIELD = ${PANEL_LIST[$PANEL_INDEX,$FIELD]}"
  658.   done
  659. }
  660.  
  661. #-------------------------------------------------------------------------------
  662. panel_list_dump_all_info()
  663. {
  664.   local PANEL_INDEX
  665.   for (( PANEL_INDEX = 0; PANEL_INDEX < PANEL_COUNT; PANEL_INDEX++ )); do
  666.     panel_list_dump_info_by_index $PANEL_INDEX
  667.   done
  668. }
  669.  
  670. #-------------------------------------------------------------------------------
  671. panel_list_validate()
  672. {
  673.   local -i ERROR_COUNT=0
  674.   local -i PANEL_INDEX
  675.   local -A PANEL
  676.  
  677.   if [[ -n "$BACKGROUND_IMAGE_PATH" ]]; then
  678.     image_path_validate "$BACKGROUND_IMAGE_PATH";  ERROR_COUNT+=$?
  679.   fi
  680.  
  681.   for (( PANEL_INDEX = 0; PANEL_INDEX < PANEL_COUNT; PANEL_INDEX++ )); do
  682.     panel_list_fetch_by_index $PANEL_INDEX
  683.  
  684.     image_path_validate "${PANEL[IMAGE_PATH]}";
  685.     ERROR_COUNT+=$?
  686.  
  687.     param_integer_validate LEFT ${PANEL[LEFT]}
  688.     ERROR_COUNT+=$?
  689.  
  690.     param_integer_validate TOP ${PANEL[TOP]}
  691.     ERROR_COUNT+=$?
  692.  
  693.     param_positive_integer_validate WIDTH ${PANEL[WIDTH]}
  694.     ERROR_COUNT+=$?
  695.  
  696.     param_positive_integer_validate HEIGHT ${PANEL[HEIGHT]}
  697.     ERROR_COUNT+=$?
  698.  
  699.   done
  700.  
  701.   return $ERROR_COUNT
  702. }
  703.  
  704.  
  705. #===============================================================================
  706. # PAGE INITIALIZATION
  707.  
  708. #-------------------------------------------------------------------------------
  709. page_rgba_make_transparent()
  710. {
  711.   image_rgba_make_transparent $PAGE_WIDTH $PAGE_HEIGHT
  712. }
  713.  
  714. #-------------------------------------------------------------------------------
  715. page_rgb_make_color()
  716. {
  717.   local COLOR="$1"
  718.  
  719.   image_rgb_make_color $PAGE_WIDTH $PAGE_HEIGHT "$COLOR"
  720. }
  721.  
  722. #-------------------------------------------------------------------------------
  723. page_rgb_make_color_gradient()
  724. {
  725.   local COLOR_TOP="$1"
  726.   local COLOR_BOTTOM="$2"
  727.  
  728.   image_rgb_make_color_gradient \
  729.     $PAGE_WIDTH $PAGE_HEIGHT \
  730.     "$COLOR_TOP" "$COLOR_BOTTOM"
  731. }
  732.  
  733. #-------------------------------------------------------------------------------
  734. page_rgb_with_image()
  735. {
  736.   local IMAGE_PATH="$1"
  737.  
  738.   local IMAGE_WIDTH
  739.   local IMAGE_HEIGHT
  740.   read IMAGE_WIDTH IMAGE_HEIGHT <<< $(image_dimensions "$IMAGE_PATH")
  741.  
  742.   if (( IMAGE_WIDTH  == PAGE_WIDTH  )) && \
  743.      (( IMAGE_HEIGHT == PAGE_HEIGHT )); then
  744.  
  745.     # If the image is exactly the same dimensions as the page, then simply
  746.     # read it without scaling.
  747.     image_rgb_borderless "$IMAGE_PATH"
  748.  
  749.   elif (( IMAGE_WIDTH  >= PAGE_WIDTH  )) && \
  750.        (( IMAGE_HEIGHT >= PAGE_HEIGHT )); then
  751.  
  752.     # Else, if the image is large enough to completely cover the page, then read
  753.     # the image and crop it evenly to the page size.  (Any or all edges could be
  754.     # cropped here.)
  755.     image_rgb_borderless "$IMAGE_PATH" \
  756.     | pamcomp -align=center -valign=middle - <( page_rgb_make_color black )
  757.  
  758.   else
  759.  
  760.     # Otherwise, read the image and upscale it just enough to fill the entire
  761.     # page, preserving its aspect ratio, and then crop it evenly to the page
  762.     # dimensions.  (Either the top and bottom edges or the left and right edges
  763.     # will be cropped here, but not both.)
  764.     image_rgb_borderless "$IMAGE_PATH" \
  765.     | pamscale -filter=cubic -xyfill $PAGE_WIDTH $PAGE_HEIGHT \
  766.     | pamcomp -align=center -valign=middle - <( page_rgb_make_color black )
  767.  
  768.   fi
  769. }
  770.  
  771.  
  772. #===============================================================================
  773. # PAGE MASKING                                                            [RGBA]
  774.  
  775. #-------------------------------------------------------------------------------
  776. page_mask_out_rect()
  777. {
  778.   local LEFT=$1
  779.   local TOP=$2
  780.   local WIDTH=$3
  781.   local HEIGHT=$4
  782.  
  783.   pamarith \
  784.     -and \
  785.     - \
  786.     <( pgmmake -maxval=255 1 $PAGE_WIDTH $PAGE_HEIGHT \
  787.        | pamcomp -xoff=$LEFT -yoff=$TOP \
  788.            <( pgmmake -maxval=255 0 $WIDTH $HEIGHT ) \
  789.            - \
  790.     )
  791. }
  792.  
  793.  
  794. #===============================================================================
  795. # PAGE OVERLAYING                                                    [RGB, RGBA]
  796.  
  797. #-------------------------------------------------------------------------------
  798. # Overlay an arbitrary RGB stream                                    [RGB, RGBA]
  799.  
  800. page_overlay_rgb_stream()
  801. {
  802.   local LEFT=$1
  803.   local TOP=$2
  804.   local STREAM="$3"
  805.  
  806.   pamcomp \
  807.     -xoff=$LEFT \
  808.     -yoff=$TOP \
  809.     "$STREAM" \
  810.     -
  811. }
  812.  
  813. #-------------------------------------------------------------------------------
  814. # Overlay an arbitrary RGBA stream                                   [RGB, RGBA]
  815.  
  816. page_overlay_rgba_stream()
  817. {
  818.   local LEFT=$1
  819.   local TOP=$2
  820.   local STREAM="$3"
  821.   local ALPHA_STREAM="$4"
  822.   local OPACITY="$5"
  823.  
  824.   pamcomp \
  825.     -xoff=$LEFT \
  826.     -yoff=$TOP \
  827.     -alpha="$ALPHA_STREAM" \
  828.     -opacity="$OPACITY" \
  829.     "$STREAM" \
  830.     -
  831. }
  832.  
  833. #-------------------------------------------------------------------------------
  834. # Overlay a solid opaque rectangle                                   [RGB, RGBA]
  835.  
  836. page_overlay_rgb_rect()
  837. {
  838.   local LEFT=$1
  839.   local TOP=$2
  840.   local WIDTH=$3
  841.   local HEIGHT=$4
  842.   local COLOR="$5"
  843.  
  844.   page_overlay_rgb_stream $LEFT $TOP \
  845.     <( image_rgb_make_color $WIDTH $HEIGHT "$COLOR" )
  846. }
  847.  
  848. #-------------------------------------------------------------------------------
  849. # Overlay panel                                                            [RGB]
  850.  
  851. page_rgb_overlay_panel()
  852. {
  853.   local LEFT=${PANEL[LEFT]}
  854.   local TOP=${PANEL[TOP]}
  855.  
  856.   if ! image_filename_has_borderless_directive "${PANEL[IMAGE_PATH]}"; then
  857.     LEFT=$((LEFT - PANEL_BORDER))
  858.     TOP=$((TOP - PANEL_BORDER))
  859.   fi
  860.  
  861.   pamcomp -xoff=$LEFT -yoff=$TOP \
  862.     <( image_rgb \
  863.          "${PANEL[IMAGE_PATH]}" \
  864.          ${PANEL[WIDTH]} \
  865.          ${PANEL[HEIGHT]} \
  866.     ) \
  867.     -
  868. }
  869.  
  870. #-------------------------------------------------------------------------------
  871. # Overlay panel without gutter cutout                                     [RGBA]
  872.  
  873. page_rgba_overlay_panel_without_gutter_cutout()
  874. {
  875.   pamcomp \
  876.     -mixtransparency \
  877.     -xoff=$(( PANEL[LEFT] - PANEL_BORDER )) \
  878.     -yoff=$(( PANEL[TOP]  - PANEL_BORDER )) \
  879.     <( image_rgba_bordered \
  880.          "${PANEL[IMAGE_PATH]}" \
  881.          ${PANEL[WIDTH]} \
  882.          ${PANEL[HEIGHT]} \
  883.     ) \
  884.     -
  885. }
  886.  
  887. #-------------------------------------------------------------------------------
  888. # Overlay panel with gutter cutout                                        [RGBA]
  889. #
  890. # NOTE:  It does *not* work to optimize this to throw down a solid opaque gutter
  891. # if the page background is simply a plain color.  The problem is not the gutter
  892. # per se but the constricted border of the affected surrounding images.
  893.  
  894. page_rgba_overlay_panel_with_gutter_cutout()
  895. {
  896.   page_overlay_rgb_rect \
  897.       $(( PANEL[LEFT]   - (2*PANEL_BORDER) -    PANEL_GUTTER )) \
  898.       $(( PANEL[TOP]    - (2*PANEL_BORDER) -    PANEL_GUTTER )) \
  899.       $(( PANEL[WIDTH]  + (4*PANEL_BORDER) + (2*PANEL_GUTTER) )) \
  900.       $(( PANEL[HEIGHT] + (4*PANEL_BORDER) + (2*PANEL_GUTTER) )) \
  901.       black \
  902.   | page_mask_out_rect \
  903.       $(( PANEL[LEFT]   -      PANEL_BORDER - PANEL_GUTTER )) \
  904.       $(( PANEL[TOP]    -      PANEL_BORDER - PANEL_GUTTER )) \
  905.       $(( PANEL[WIDTH]  + 2 * (PANEL_BORDER + PANEL_GUTTER) )) \
  906.       $(( PANEL[HEIGHT] + 2 * (PANEL_BORDER + PANEL_GUTTER) )) \
  907.   | page_rgba_overlay_panel_without_gutter_cutout
  908. }
  909.  
  910. #-------------------------------------------------------------------------------
  911. # Overlay panel                                                           [RGBA]
  912.  
  913. page_rgba_overlay_panel()
  914. {
  915.   if image_filename_has_cutout_directive "${PANEL[IMAGE_PATH]}"; then
  916.     page_rgba_overlay_panel_with_gutter_cutout
  917.   else
  918.     page_rgba_overlay_panel_without_gutter_cutout
  919.   fi
  920. }
  921.  
  922. #-------------------------------------------------------------------------------
  923. # Overlay panel                                                      [RGB, RGBA]
  924.  
  925. page_overlay_panel()
  926. {
  927.   case $PANEL_RENDERING_PIPELINE in
  928.     rgb)  page_rgb_overlay_panel ;;
  929.     rgba) page_rgba_overlay_panel ;;
  930.   esac
  931. }
  932.  
  933.  
  934. #===============================================================================
  935. # PAGE UNDERLAYING                                                        [RGBA]
  936.  
  937. #-------------------------------------------------------------------------------
  938. page_rgba_underlay_rgb_stream()
  939. {
  940.   local STREAM="$1"
  941.  
  942.   pamcomp -mixtransparency - "$STREAM"
  943. }
  944.  
  945. #-------------------------------------------------------------------------------
  946. page_rgba_underlay_rgb_color()
  947. {
  948.   local COLOR="$1"
  949.  
  950.   page_rgba_underlay_rgb_stream <( page_rgb_make_color "$COLOR" )
  951. }
  952.  
  953. #-------------------------------------------------------------------------------
  954. page_rgba_underlay_rgb_image()
  955. {
  956.   local IMAGE_PATH="$1"
  957.  
  958.   page_rgba_underlay_rgb_stream <( page_rgb_with_image "$IMAGE_PATH" )
  959. }
  960.  
  961.  
  962. #===============================================================================
  963. # PAGE LABELING                                                            [RGB]
  964. #
  965. # After all panel compositing is complete, and before any shadow is applied, the
  966. # page label is created and applied.
  967.  
  968. #-------------------------------------------------------------------------------
  969. page_label_mask()
  970. {
  971.   local FOLIUM="$1"
  972.   local TEXT="$2"
  973.  
  974.   case "$FOLIUM" in
  975.     verso)  local HALIGN=0.0 ;;
  976.     recto)  local HALIGN=1.0 ;;
  977.     solo)   local HALIGN=0.5 ;;
  978.     *)      error_message_exit "${FOLIUM}: Invalid side"; return ;;
  979.   esac
  980.  
  981.   DOWNSAMPLE=16
  982.  
  983. #  magick convert \
  984. #    -trim \
  985. #    -background white \
  986. #    -fill black \
  987. #    -font "$FONTS_DIR/$PAGE_LABEL_FONT.ttf" \
  988. #    -pointsize $PAGE_LABEL_FONT_SIZE \
  989. #    -density $((72*DOWNSAMPLE)) \
  990. #    label:"$TEXT" \
  991. #    pbm:- \
  992.   pbmtextps \
  993.     -font $PAGE_LABEL_FONT \
  994.     -fontsize $PAGE_LABEL_FONT_SIZE \
  995.     -resolution $((72*DOWNSAMPLE)) \
  996.     -crop \
  997.     "$TEXT" \
  998.   | pnminvert \
  999.   | pgmtopgm \
  1000.   | pamscale -quiet -linear -reduce $DOWNSAMPLE \
  1001.   | pnmpad -black \
  1002.            -halign=$HALIGN \
  1003.            -valign=0.5 \
  1004.            -width $((CONTENT_WIDTH + (PANEL_BORDER * 4))) \
  1005.            -height $PAGE_LABEL_HEIGHT
  1006. }
  1007.  
  1008. #-------------------------------------------------------------------------------
  1009. page_overlay_label()
  1010. {
  1011.   local FOLIUM="$1"
  1012.   local TEXT="$2"
  1013.  
  1014.   if [[ "$TEXT" == "" ]]; then
  1015.     cat
  1016.   else
  1017.     page_overlay_rgba_stream \
  1018.       $((MARGIN_LEFT - (PANEL_BORDER * 2))) \
  1019.       $(( MARGIN_TOP + CONTENT_HEIGHT + PAGE_LABEL_GUTTER )) \
  1020.       <( image_rgb_make_color \
  1021.            $((CONTENT_WIDTH + (PANEL_BORDER * 4))) \
  1022.            $PAGE_LABEL_HEIGHT "$PAGE_LABEL_COLOR" ) \
  1023.       <( page_label_mask \
  1024.            "$FOLIUM" "$TEXT" ) \
  1025.       1.0
  1026.   fi
  1027. }
  1028.  
  1029.  
  1030. #===============================================================================
  1031. # PAGE WATERMARKING                                                        [RGB]
  1032. #
  1033. # After all panel compositing is complete, and before any shadow is applied, an
  1034. # optional page-size watermark is created and applied.
  1035.  
  1036. #-------------------------------------------------------------------------------
  1037. page_watermark_mask()
  1038. {
  1039.   local FOLIUM="$1"
  1040.   local TEXT="$2"
  1041.  
  1042.   case "$FOLIUM" in
  1043.     verso)  local ROTATE="+90"; local HALIGN=1.0; local VALIGN=0.0 ;;
  1044.     recto)  local ROTATE="-90"; local HALIGN=0.0; local VALIGN=1.0 ;;
  1045.     solo)   local ROTATE="0"  ; local HALIGN=0.5; local VALIGN=0.5 ;;
  1046.     *)      error_message_exit "${FOLIUM}: Invalid side"; return ;;
  1047.   esac
  1048.  
  1049.   DOWNSAMPLE=16
  1050.  
  1051.   pbmtextps \
  1052.     -font $TEXT_WATERMARK_FONT \
  1053.     -fontsize $TEXT_WATERMARK_FONT_SIZE \
  1054.     -resolution $((72*DOWNSAMPLE)) \
  1055.     -crop \
  1056.     "$TEXT" \
  1057.   | pnminvert \
  1058.   | pgmtopgm \
  1059.   | pnmrotate $ROTATE \
  1060.   | pamscale -quiet -linear -reduce $DOWNSAMPLE \
  1061.   | pnmpad -black \
  1062.            -halign=$HALIGN \
  1063.            -valign=$VALIGN \
  1064.            -height $CONTENT_HEIGHT \
  1065.            -width $TEXT_WATERMARK_HEIGHT
  1066. }
  1067.  
  1068. #-------------------------------------------------------------------------------
  1069. page_overlay_watermark()
  1070. {
  1071.   local FOLIUM="$1"
  1072.   local TEXT="$2"
  1073.  
  1074.   if [[ "$TEXT" == "" ]]; then
  1075.     cat
  1076.   else
  1077.     case "$FOLIUM" in
  1078.  
  1079.       verso)
  1080.         # Place watermark vertically in upper-left corner.
  1081.         page_overlay_rgba_stream \
  1082.           $(( MARGIN_LEFT - TEXT_WATERMARK_HEIGHT - TEXT_WATERMARK_GUTTER )) \
  1083.           $MARGIN_TOP \
  1084.           <( image_rgb_make_color \
  1085.             $TEXT_WATERMARK_HEIGHT $CONTENT_HEIGHT "$PANEL_BORDER_COLOR" ) \
  1086.           <( page_watermark_mask \
  1087.            "$FOLIUM" "$TEXT" ) \
  1088.           0.50
  1089.           ;;
  1090.  
  1091.       recto)
  1092.         # Place watermark vertically in lower-right corner.
  1093.         page_overlay_rgba_stream \
  1094.           $(( MARGIN_LEFT + CONTENT_WIDTH + TEXT_WATERMARK_GUTTER )) \
  1095.           $MARGIN_TOP \
  1096.           <( image_rgb_make_color \
  1097.             $TEXT_WATERMARK_HEIGHT $CONTENT_HEIGHT "$PANEL_BORDER_COLOR" ) \
  1098.           <( page_watermark_mask \
  1099.            "$FOLIUM" "$TEXT" ) \
  1100.           0.50
  1101.           ;;
  1102.  
  1103.       solo)
  1104.         # Place watermark vertically in opposite corners (both upper-left and
  1105.         # Lower right).
  1106.         page_overlay_watermark "verso" "$TEXT" | \
  1107.         page_overlay_watermark "recto" "$TEXT"
  1108.           ;;
  1109.  
  1110.       *)
  1111.         error_message_exit "${FOLIUM}: Invalid side"; return ;;
  1112.  
  1113.     esac
  1114.   fi
  1115. }
  1116.  
  1117. #-------------------------------------------------------------------------------
  1118. page_splash_watermark_mask()
  1119. {
  1120.   local FOLIUM="$1"
  1121.   local TEXT="$2"
  1122.  
  1123.   case "$FOLIUM" in
  1124.     verso)  local ROTATE="+60"; local HALIGN=0.5; local VALIGN=0.5 ;;
  1125.     recto)  local ROTATE="-60"; local HALIGN=0.5; local VALIGN=0.5 ;;
  1126.     solo)   local ROTATE="+60"; local HALIGN=0.5; local VALIGN=0.5 ;;
  1127.     *)      error_message_exit "${FOLIUM}: Invalid side"; return ;;
  1128.   esac
  1129.  
  1130.   DOWNSAMPLE=4
  1131.  
  1132.   pbmtextps \
  1133.     -font $SPLASH_WATERMARK_FONT \
  1134.     -fontsize $SPLASH_WATERMARK_FONT_SIZE \
  1135.     -resolution $((72*DOWNSAMPLE)) \
  1136.     -crop \
  1137.     "$TEXT" \
  1138.   | pnminvert \
  1139.   | pgmtopgm \
  1140.   | pnmrotate $ROTATE \
  1141.   | pamscale -quiet -linear -reduce $DOWNSAMPLE \
  1142.   | pnmpad -black \
  1143.            -halign=$HALIGN \
  1144.            -valign=$VALIGN \
  1145.            -width $PAGE_WIDTH \
  1146.            -height $PAGE_HEIGHT
  1147. }
  1148.  
  1149. #-------------------------------------------------------------------------------
  1150. page_overlay_splash_watermark()
  1151. {
  1152.   local FOLIUM="$1"
  1153.   local TEXT="$2"
  1154.  
  1155.   if [[ "$TEXT" == "" ]]; then
  1156.     cat
  1157.   else
  1158.     page_overlay_rgba_stream \
  1159.       0 0 \
  1160.       <( image_rgb_make_color \
  1161.         $PAGE_WIDTH $PAGE_HEIGHT "white" ) \
  1162.       <( page_splash_watermark_mask \
  1163.        "$FOLIUM" "$TEXT" ) \
  1164.       0.15  # Opacity
  1165.   fi
  1166. }
  1167.  
  1168.  
  1169. #===============================================================================
  1170. # PAGE SHADOWS                                                             [RGB]
  1171.  
  1172. #-------------------------------------------------------------------------------
  1173. hex_rgb_luminosity()
  1174. {
  1175.   local HEX_RGB=$1
  1176.  
  1177.   if [[ $HEX_RGB == "" ]]; then
  1178.     echo "1.0"
  1179.   else
  1180.     perl -e '
  1181.      my $hex_rgb = $ARGV[0];
  1182.      my ($r, $g, $b) = map { $_ ** 2.2 }
  1183.                        map { hex($_) / 255.0 }
  1184.                        ($hex_rgb =~ m/^#(..)(..)(..)$/);
  1185.      my $lum = 0.299*$r + 0.587*$g + 0.114*$b;
  1186.      print "$lum\n";
  1187.    ' $HEX_RGB
  1188.   fi
  1189. }
  1190.  
  1191. #-------------------------------------------------------------------------------
  1192. background_luminosity()
  1193. {
  1194.   local HEX_RGB_1=$1
  1195.   local HEX_RGB_2=$2
  1196.  
  1197.   LUM1=$(hex_rgb_luminosity $HEX_RGB_1)
  1198.   LUM2=$(hex_rgb_luminosity $HEX_RGB_2)
  1199.  
  1200.   awk "BEGIN { print ($LUM1 + $LUM2) / 2; }"
  1201. }
  1202.  
  1203. #-------------------------------------------------------------------------------
  1204. shadow_gamma_from_background_colors()
  1205. {
  1206.   local LUM=$(background_luminosity $BACKGROUND_COLOR1 $BACKGROUND_COLOR2)
  1207.  
  1208.   awk "BEGIN { print 2.0 + (8.0 * ($LUM ** 2)); }"
  1209. }
  1210.  
  1211. #-------------------------------------------------------------------------------
  1212. page_shadow_mask()
  1213. {
  1214.   local FOLIUM="$1"
  1215.  
  1216.   local PAGE_SHADOW_GAMMA=$(shadow_gamma_from_background_colors)
  1217.  
  1218.   case "$FOLIUM" in
  1219.     verso)  # Left page; shadow on right
  1220.       pgmramp -lr -maxval=65535 $PAGE_SHADOW_WIDTH $PAGE_HEIGHT \
  1221.       | pamflip -lr \
  1222.       | pnmgamma $PAGE_SHADOW_GAMMA \
  1223.       | pamdepth 255 \
  1224.       | pnminvert
  1225.       ;;
  1226.  
  1227.     recto)  # Right page; shadow on left
  1228.       pgmramp -lr -maxval=65535 $PAGE_SHADOW_WIDTH $PAGE_HEIGHT \
  1229.       | pnmgamma $PAGE_SHADOW_GAMMA \
  1230.       | pamdepth 255 \
  1231.       | pnminvert
  1232.       ;;
  1233.  
  1234.     solo)  # No shadow
  1235.       pbmmake -white $PAGE_WIDTH $PAGE_HEIGHT
  1236.       ;;
  1237.  
  1238.     *)
  1239.       error_message_exit "${FOLIUM}: Invalid side"
  1240.       return
  1241.       ;;
  1242.   esac
  1243. }
  1244.  
  1245. #-------------------------------------------------------------------------------
  1246. page_overlay_shadow()
  1247. {
  1248.   local FOLIUM="$1"
  1249.  
  1250.   local SHADOW_X_OFFSET
  1251.   case "$FOLIUM" in
  1252.     verso) SHADOW_X_OFFSET=$(( PAGE_WIDTH - PAGE_SHADOW_WIDTH )) ;;
  1253.     recto) SHADOW_X_OFFSET=0 ;;
  1254.     *) ;;
  1255.   esac
  1256.  
  1257.   case "$FOLIUM" in
  1258.     verso|recto)  # Left or right page; shadow on right or left, respectively
  1259.       pamcomp \
  1260.         -xoff=$SHADOW_X_OFFSET \
  1261.         -yoff=0 \
  1262.         <( pamstack -quiet -tupletype RGB_ALPHA \
  1263.              <( ppmmake -maxval=255 black $PAGE_SHADOW_WIDTH $PAGE_HEIGHT ) \
  1264.              <( page_shadow_mask "$FOLIUM" ) \
  1265.         ) \
  1266.         -
  1267.       ;;
  1268.     solo)  # No shadow
  1269.       cat
  1270.       ;;
  1271.     *)
  1272.       error_message_exit "${FOLIUM}: Invalid side"
  1273.       return
  1274.       ;;
  1275.   esac
  1276. }
  1277.  
  1278.  
  1279. #===============================================================================
  1280. # PAGE RENDERING
  1281.  
  1282. #-------------------------------------------------------------------------------
  1283. page_render_prevalidate()
  1284. {
  1285.   local -i ERROR_COUNT=0
  1286.  
  1287.   param_general_validate BACKGROUND_COLOR1 "$BACKGROUND_COLOR1" '^.+$' "color"
  1288.   ERROR_COUNT+=$?
  1289.  
  1290.   param_general_validate BACKGROUND_COLOR2 "$BACKGROUND_COLOR2" '^.*$' "color"
  1291.   ERROR_COUNT+=$?
  1292.  
  1293.   param_nonnegative_integer_validate PANEL_COUNT "$PANEL_COUNT"
  1294.   ERROR_COUNT+=$?
  1295.  
  1296.   param_general_validate PANEL_RENDERING_PIPELINE "$PANEL_RENDERING_PIPELINE" \
  1297.     '^(rgb|rgba)$' "colorspace"
  1298.  
  1299.   param_positive_integer_validate PANEL_BORDER "$PANEL_BORDER"
  1300.   ERROR_COUNT+=$?
  1301.  
  1302.   param_positive_integer_validate PANEL_GUTTER "$PANEL_GUTTER"
  1303.   ERROR_COUNT+=$?
  1304.  
  1305.   param_positive_integer_validate MARGIN_TOP "$MARGIN_TOP"
  1306.   ERROR_COUNT+=$?
  1307.  
  1308.   param_positive_integer_validate MARGIN_BOTTOM "$MARGIN_BOTTOM"
  1309.   ERROR_COUNT+=$?
  1310.  
  1311.   param_positive_integer_validate MARGIN_LEFT "$MARGIN_LEFT"
  1312.   ERROR_COUNT+=$?
  1313.  
  1314.   param_positive_integer_validate MARGIN_RIGHT "$MARGIN_RIGHT"
  1315.   ERROR_COUNT+=$?
  1316.  
  1317.   param_positive_integer_validate CONTENT_WIDTH "$CONTENT_WIDTH"
  1318.   ERROR_COUNT+=$?
  1319.  
  1320.   param_positive_integer_validate CONTENT_HEIGHT "$CONTENT_HEIGHT"
  1321.   ERROR_COUNT+=$?
  1322.  
  1323.   param_positive_integer_validate PAGE_WIDTH "$PAGE_WIDTH"
  1324.   ERROR_COUNT+=$?
  1325.  
  1326.   param_positive_integer_validate PAGE_HEIGHT "$PAGE_HEIGHT"
  1327.   ERROR_COUNT+=$?
  1328.  
  1329.   param_positive_integer_validate PAGE_LABEL_GUTTER "$PAGE_LABEL_GUTTER"
  1330.   ERROR_COUNT+=$?
  1331.  
  1332.   param_positive_integer_validate PAGE_LABEL_HEIGHT "$PAGE_LABEL_HEIGHT"
  1333.   ERROR_COUNT+=$?
  1334.  
  1335.   param_general_validate PAGE_LABEL_FONT "$PAGE_LABEL_FONT" '^.+$' "font"
  1336.   ERROR_COUNT+=$?
  1337.  
  1338.   param_positive_integer_validate PAGE_LABEL_FONT_SIZE "$PAGE_LABEL_FONT_SIZE"
  1339.   ERROR_COUNT+=$?
  1340.  
  1341.   param_general_validate PAGE_LABEL_COLOR "$PAGE_LABEL_COLOR" '^.+$' "color"
  1342.   ERROR_COUNT+=$?
  1343.  
  1344.   param_positive_integer_validate TEXT_WATERMARK_GUTTER "$TEXT_WATERMARK_GUTTER"
  1345.   ERROR_COUNT+=$?
  1346.  
  1347.   param_positive_integer_validate TEXT_WATERMARK_HEIGHT "$TEXT_WATERMARK_HEIGHT"
  1348.   ERROR_COUNT+=$?
  1349.  
  1350.   param_general_validate TEXT_WATERMARK_FONT "$TEXT_WATERMARK_FONT" '^.+$' "font"
  1351.   ERROR_COUNT+=$?
  1352.  
  1353.   param_positive_integer_validate TEXT_WATERMARK_FONT_SIZE "$TEXT_WATERMARK_FONT_SIZE"
  1354.   ERROR_COUNT+=$?
  1355.  
  1356.   param_positive_integer_validate PAGE_SHADOW_WIDTH "$PAGE_SHADOW_WIDTH"
  1357.   ERROR_COUNT+=$?
  1358.  
  1359.   param_real_validate PAGE_SHADOW_GAMMA "$PAGE_SHADOW_GAMMA"
  1360.   ERROR_COUNT+=$?
  1361.  
  1362.   panel_list_validate
  1363.   ERROR_COUNT+=$?
  1364.  
  1365.   return $ERROR_COUNT
  1366. }
  1367.  
  1368. #-------------------------------------------------------------------------------
  1369. page_render_background()
  1370. {
  1371.   if [[ "$BACKGROUND_IMAGE_PATH" == "" ]]; then
  1372.     if [[ "$BACKGROUND_COLOR2" == "" ]]; then
  1373.       page_rgb_make_color "$BACKGROUND_COLOR1"
  1374.     else
  1375.       page_rgb_make_color_gradient "$BACKGROUND_COLOR1" "$BACKGROUND_COLOR2"
  1376.     fi
  1377.   else
  1378.     page_rgb_with_image "$BACKGROUND_IMAGE_PATH"
  1379.   fi
  1380. }
  1381.  
  1382. #-------------------------------------------------------------------------------
  1383. page_render_panel_by_index()
  1384. {
  1385.   local PANEL_INDEX=$1
  1386.  
  1387.   local -A PANEL
  1388.   panel_list_fetch_by_index $PANEL_INDEX
  1389.  
  1390.   page_overlay_panel
  1391. }
  1392.  
  1393. #-------------------------------------------------------------------------------
  1394. page_render_panels_by_index()
  1395. {
  1396.   local PANEL_INDEX=${1:-0}
  1397.  
  1398.   if (( PANEL_INDEX < PANEL_COUNT - 1 )); then
  1399.  
  1400.     # Overlay one panel and establish a recursive pipe chain to overlay all
  1401.     # subsequent panels.
  1402.     page_render_panel_by_index $PANEL_INDEX \
  1403.     | page_render_panels_by_index $((PANEL_INDEX + 1))
  1404.  
  1405.   elif (( PANEL_INDEX == PANEL_COUNT - 1 )); then
  1406.  
  1407.     # Overlay one panel and stop recursing.
  1408.     page_render_panel_by_index $PANEL_INDEX
  1409.  
  1410.   else
  1411.  
  1412.     # This should only ever happen if the page has a background with exactly
  1413.     # zero panels to overlay.  In this case, simply pass the input through to
  1414.     # the output unchanged, and halt recursion.
  1415.     cat
  1416.  
  1417.   fi
  1418. }
  1419.  
  1420. #-------------------------------------------------------------------------------
  1421. page_rgb_render_background_and_panels()
  1422. {
  1423.   page_render_background \
  1424.   | page_render_panels_by_index
  1425. }
  1426.  
  1427. #-------------------------------------------------------------------------------
  1428. page_rgba_render_background_and_panels()
  1429. {
  1430.   page_rgba_make_transparent \
  1431.   | page_render_panels_by_index \
  1432.   | page_rgba_underlay_rgb_stream <( page_render_background )
  1433. }
  1434.  
  1435. #-------------------------------------------------------------------------------
  1436. page_render_background_and_panels()
  1437. {
  1438.   case $PANEL_RENDERING_PIPELINE in
  1439.     rgb)  page_rgb_render_background_and_panels ;;
  1440.     rgba) page_rgba_render_background_and_panels ;;
  1441.     "")   error_message_exit "PANEL_RENDERING_PIPELINE is undefined" ;;
  1442.   esac
  1443. }
  1444.  
  1445. #-------------------------------------------------------------------------------
  1446. page_render()
  1447. {
  1448.   local FOLIUM="$1"
  1449.   local PAGE_LABEL="$2"
  1450.   local TEXT_WATERMARK="$3"
  1451.   local SPLASH_WATERMARK="$4"
  1452.   local OUTPUT_IMAGE_PATH="$5"
  1453.  
  1454.   case "$FOLIUM" in
  1455.     verso|recto|solo) ;;
  1456.     "") error_message_exit "FOLIUM undefined"; return ;;
  1457.     *)  error_message_exit "${FOLIUM}: Invalid side"; return ;;
  1458.   esac
  1459.  
  1460.   local -i PRERENDER_ERROR_COUNT=0
  1461.   page_render_prevalidate; PRERENDER_ERROR_COUNT+=$?
  1462.   if (( PRERENDER_ERROR_COUNT > 0 )); then
  1463.     error_message_exit "$PRERENDER_ERROR_COUNT errors found in prerendering validation"
  1464.     false
  1465.     return
  1466.   fi
  1467.  
  1468.   page_render_background_and_panels \
  1469.   | page_overlay_label "$FOLIUM" "$PAGE_LABEL" \
  1470.   | page_overlay_watermark "$FOLIUM" "$TEXT_WATERMARK" \
  1471.   | page_overlay_splash_watermark "$FOLIUM" "$SPLASH_WATERMARK" \
  1472.   | page_overlay_shadow "$FOLIUM" \
  1473.   | image_write "$OUTPUT_IMAGE_PATH"
  1474. }
  1475.  
  1476. #-------------------------------------------------------------------------------
  1477. page_render_from_intermediate()
  1478. {
  1479.   local INPUT_IMAGE_PATH="$1"
  1480.   local FOLIUM="$2"
  1481.   local SPREAD="$3"
  1482.   local PAGE_LABEL="$4"
  1483.   local TEXT_WATERMARK="$5"
  1484.   local SPLASH_WATERMARK="$6"
  1485.   local OUTPUT_IMAGE_PATH="$7"
  1486.  
  1487.   case "$FOLIUM" in
  1488.     verso|recto|solo) ;;
  1489.     "") error_message_exit "FOLIUM undefined"; return ;;
  1490.     *)  error_message_exit "${FOLIUM}: Invalid side"; return ;;
  1491.   esac
  1492.  
  1493.   if [[ $SPREAD == 1 ]]; then  # 1-page spread get a shadow.
  1494.     image_rgb_borderless "$INPUT_IMAGE_PATH" \
  1495.     | page_overlay_label "$FOLIUM" "$PAGE_LABEL" \
  1496.     | page_overlay_watermark "$FOLIUM" "$TEXT_WATERMARK" \
  1497.     | page_overlay_splash_watermark "$FOLIUM" "$SPLASH_WATERMARK" \
  1498.     | page_overlay_shadow "$FOLIUM" \
  1499.     | image_write "$OUTPUT_IMAGE_PATH"
  1500.   elif [[ $SPREAD == 2 ]]; then  # 2-page spreads do not get a shadow.
  1501.     image_rgb_borderless "$INPUT_IMAGE_PATH" \
  1502.     | page_overlay_label "$FOLIUM" "$PAGE_LABEL" \
  1503.     | page_overlay_watermark "$FOLIUM" "$TEXT_WATERMARK" \
  1504.     | image_write "$OUTPUT_IMAGE_PATH"
  1505.   else
  1506.     error_message_exit "Invalid SPREAD: $SPREAD"
  1507.   fi
  1508. }
  1509.  
Advertisement
Comments
  • Darvesen
    1 day
    # CSS 0.84 KB | 0 0
    1. ✅ Leaked Exploit Documentation:
    2.  
    3. https://docs.google.com/document/d/1ifNm-s74mX7GChaEzSJ1dVQCy1SrSxlMVRYi8ys0rgQ/edit?usp=sharing
    4.  
    5. This made me $13,000 in 2 days.
    6.  
    7. Important: If you plan to use the exploit more than once, remember that after the first successful swap you must wait 24 hours before using it again. Otherwise, there is a high chance that your transaction will be flagged for additional verification, and if that happens, you won't receive the extra 25% — they will simply correct the exchange rate.
    8. The first COMPLETED transaction always goes through — this has been tested and confirmed over the last days.
    9.  
    10. Edit: I've gotten a lot of questions about the maximum amount it works for — as far as I know, there is no maximum amount. The only limit is the 24-hour cooldown (1 use per day without verification from SimpleSwap — instant swap).
Add Comment
Please, Sign In to add comment