image_pack 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (319) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +18 -0
  3. data/LICENSE.txt +21 -0
  4. data/README.md +140 -0
  5. data/THIRD_PARTY_NOTICES.md +8 -0
  6. data/ext/image_pack/extconf.rb +515 -0
  7. data/ext/image_pack/image_pack.c +1618 -0
  8. data/ext/image_pack/vendor/.vendored +1 -0
  9. data/ext/image_pack/vendor/mozjpeg/BUILDING.txt +902 -0
  10. data/ext/image_pack/vendor/mozjpeg/CMakeLists.txt +1593 -0
  11. data/ext/image_pack/vendor/mozjpeg/LICENSE.md +132 -0
  12. data/ext/image_pack/vendor/mozjpeg/README-mozilla.txt +194 -0
  13. data/ext/image_pack/vendor/mozjpeg/README-turbo.txt +346 -0
  14. data/ext/image_pack/vendor/mozjpeg/README.ijg +258 -0
  15. data/ext/image_pack/vendor/mozjpeg/README.md +29 -0
  16. data/ext/image_pack/vendor/mozjpeg/cderror.h +128 -0
  17. data/ext/image_pack/vendor/mozjpeg/cdjpeg.c +156 -0
  18. data/ext/image_pack/vendor/mozjpeg/cdjpeg.h +171 -0
  19. data/ext/image_pack/vendor/mozjpeg/cjpeg.c +961 -0
  20. data/ext/image_pack/vendor/mozjpeg/cmyk.h +60 -0
  21. data/ext/image_pack/vendor/mozjpeg/coderules.txt +78 -0
  22. data/ext/image_pack/vendor/mozjpeg/croptest.in +95 -0
  23. data/ext/image_pack/vendor/mozjpeg/djpeg.c +855 -0
  24. data/ext/image_pack/vendor/mozjpeg/example.txt +464 -0
  25. data/ext/image_pack/vendor/mozjpeg/jaricom.c +157 -0
  26. data/ext/image_pack/vendor/mozjpeg/jcapimin.c +307 -0
  27. data/ext/image_pack/vendor/mozjpeg/jcapistd.c +168 -0
  28. data/ext/image_pack/vendor/mozjpeg/jcarith.c +972 -0
  29. data/ext/image_pack/vendor/mozjpeg/jccoefct.c +609 -0
  30. data/ext/image_pack/vendor/mozjpeg/jccolext.c +144 -0
  31. data/ext/image_pack/vendor/mozjpeg/jccolor.c +721 -0
  32. data/ext/image_pack/vendor/mozjpeg/jcdctmgr.c +1776 -0
  33. data/ext/image_pack/vendor/mozjpeg/jcext.c +219 -0
  34. data/ext/image_pack/vendor/mozjpeg/jchuff.c +1146 -0
  35. data/ext/image_pack/vendor/mozjpeg/jchuff.h +57 -0
  36. data/ext/image_pack/vendor/mozjpeg/jcicc.c +105 -0
  37. data/ext/image_pack/vendor/mozjpeg/jcinit.c +82 -0
  38. data/ext/image_pack/vendor/mozjpeg/jcmainct.c +162 -0
  39. data/ext/image_pack/vendor/mozjpeg/jcmarker.c +844 -0
  40. data/ext/image_pack/vendor/mozjpeg/jcmaster.c +958 -0
  41. data/ext/image_pack/vendor/mozjpeg/jcmaster.h +56 -0
  42. data/ext/image_pack/vendor/mozjpeg/jcomapi.c +109 -0
  43. data/ext/image_pack/vendor/mozjpeg/jconfig.h.in +37 -0
  44. data/ext/image_pack/vendor/mozjpeg/jconfig.txt +93 -0
  45. data/ext/image_pack/vendor/mozjpeg/jconfigint.h.in +44 -0
  46. data/ext/image_pack/vendor/mozjpeg/jcparam.c +991 -0
  47. data/ext/image_pack/vendor/mozjpeg/jcphuff.c +1123 -0
  48. data/ext/image_pack/vendor/mozjpeg/jcprepct.c +351 -0
  49. data/ext/image_pack/vendor/mozjpeg/jcsample.c +522 -0
  50. data/ext/image_pack/vendor/mozjpeg/jcstest.c +126 -0
  51. data/ext/image_pack/vendor/mozjpeg/jctrans.c +408 -0
  52. data/ext/image_pack/vendor/mozjpeg/jdapimin.c +407 -0
  53. data/ext/image_pack/vendor/mozjpeg/jdapistd.c +691 -0
  54. data/ext/image_pack/vendor/mozjpeg/jdarith.c +782 -0
  55. data/ext/image_pack/vendor/mozjpeg/jdatadst-tj.c +198 -0
  56. data/ext/image_pack/vendor/mozjpeg/jdatadst.c +299 -0
  57. data/ext/image_pack/vendor/mozjpeg/jdatasrc-tj.c +194 -0
  58. data/ext/image_pack/vendor/mozjpeg/jdatasrc.c +295 -0
  59. data/ext/image_pack/vendor/mozjpeg/jdcoefct.c +881 -0
  60. data/ext/image_pack/vendor/mozjpeg/jdcoefct.h +83 -0
  61. data/ext/image_pack/vendor/mozjpeg/jdcol565.c +384 -0
  62. data/ext/image_pack/vendor/mozjpeg/jdcolext.c +141 -0
  63. data/ext/image_pack/vendor/mozjpeg/jdcolor.c +881 -0
  64. data/ext/image_pack/vendor/mozjpeg/jdct.h +208 -0
  65. data/ext/image_pack/vendor/mozjpeg/jddctmgr.c +367 -0
  66. data/ext/image_pack/vendor/mozjpeg/jdhuff.c +834 -0
  67. data/ext/image_pack/vendor/mozjpeg/jdhuff.h +247 -0
  68. data/ext/image_pack/vendor/mozjpeg/jdicc.c +167 -0
  69. data/ext/image_pack/vendor/mozjpeg/jdinput.c +408 -0
  70. data/ext/image_pack/vendor/mozjpeg/jdmainct.c +460 -0
  71. data/ext/image_pack/vendor/mozjpeg/jdmainct.h +71 -0
  72. data/ext/image_pack/vendor/mozjpeg/jdmarker.c +1374 -0
  73. data/ext/image_pack/vendor/mozjpeg/jdmaster.c +727 -0
  74. data/ext/image_pack/vendor/mozjpeg/jdmaster.h +33 -0
  75. data/ext/image_pack/vendor/mozjpeg/jdmerge.c +587 -0
  76. data/ext/image_pack/vendor/mozjpeg/jdmerge.h +47 -0
  77. data/ext/image_pack/vendor/mozjpeg/jdmrg565.c +354 -0
  78. data/ext/image_pack/vendor/mozjpeg/jdmrgext.c +184 -0
  79. data/ext/image_pack/vendor/mozjpeg/jdphuff.c +679 -0
  80. data/ext/image_pack/vendor/mozjpeg/jdpostct.c +294 -0
  81. data/ext/image_pack/vendor/mozjpeg/jdsample.c +524 -0
  82. data/ext/image_pack/vendor/mozjpeg/jdsample.h +50 -0
  83. data/ext/image_pack/vendor/mozjpeg/jdtrans.c +156 -0
  84. data/ext/image_pack/vendor/mozjpeg/jerror.c +251 -0
  85. data/ext/image_pack/vendor/mozjpeg/jerror.h +335 -0
  86. data/ext/image_pack/vendor/mozjpeg/jfdctflt.c +169 -0
  87. data/ext/image_pack/vendor/mozjpeg/jfdctfst.c +227 -0
  88. data/ext/image_pack/vendor/mozjpeg/jfdctint.c +288 -0
  89. data/ext/image_pack/vendor/mozjpeg/jidctflt.c +240 -0
  90. data/ext/image_pack/vendor/mozjpeg/jidctfst.c +371 -0
  91. data/ext/image_pack/vendor/mozjpeg/jidctint.c +2627 -0
  92. data/ext/image_pack/vendor/mozjpeg/jidctred.c +409 -0
  93. data/ext/image_pack/vendor/mozjpeg/jinclude.h +147 -0
  94. data/ext/image_pack/vendor/mozjpeg/jmemmgr.c +1180 -0
  95. data/ext/image_pack/vendor/mozjpeg/jmemnobs.c +110 -0
  96. data/ext/image_pack/vendor/mozjpeg/jmemsys.h +178 -0
  97. data/ext/image_pack/vendor/mozjpeg/jmorecfg.h +382 -0
  98. data/ext/image_pack/vendor/mozjpeg/jpeg_nbits_table.h +4098 -0
  99. data/ext/image_pack/vendor/mozjpeg/jpegcomp.h +32 -0
  100. data/ext/image_pack/vendor/mozjpeg/jpegint.h +453 -0
  101. data/ext/image_pack/vendor/mozjpeg/jpeglib.h +1211 -0
  102. data/ext/image_pack/vendor/mozjpeg/jpegtran.c +827 -0
  103. data/ext/image_pack/vendor/mozjpeg/jpegyuv.c +172 -0
  104. data/ext/image_pack/vendor/mozjpeg/jquant1.c +856 -0
  105. data/ext/image_pack/vendor/mozjpeg/jquant2.c +1286 -0
  106. data/ext/image_pack/vendor/mozjpeg/jsimd.h +123 -0
  107. data/ext/image_pack/vendor/mozjpeg/jsimd_none.c +431 -0
  108. data/ext/image_pack/vendor/mozjpeg/jsimddct.h +70 -0
  109. data/ext/image_pack/vendor/mozjpeg/jstdhuff.c +144 -0
  110. data/ext/image_pack/vendor/mozjpeg/jutils.c +133 -0
  111. data/ext/image_pack/vendor/mozjpeg/jversion.h.in +56 -0
  112. data/ext/image_pack/vendor/mozjpeg/libjpeg.map.in +11 -0
  113. data/ext/image_pack/vendor/mozjpeg/libjpeg.txt +3150 -0
  114. data/ext/image_pack/vendor/mozjpeg/rdbmp.c +690 -0
  115. data/ext/image_pack/vendor/mozjpeg/rdcolmap.c +253 -0
  116. data/ext/image_pack/vendor/mozjpeg/rdgif.c +720 -0
  117. data/ext/image_pack/vendor/mozjpeg/rdjpeg.c +160 -0
  118. data/ext/image_pack/vendor/mozjpeg/rdjpgcom.c +494 -0
  119. data/ext/image_pack/vendor/mozjpeg/rdpng.c +194 -0
  120. data/ext/image_pack/vendor/mozjpeg/rdppm.c +781 -0
  121. data/ext/image_pack/vendor/mozjpeg/rdswitch.c +642 -0
  122. data/ext/image_pack/vendor/mozjpeg/rdtarga.c +508 -0
  123. data/ext/image_pack/vendor/mozjpeg/simd/arm/aarch32/jccolext-neon.c +148 -0
  124. data/ext/image_pack/vendor/mozjpeg/simd/arm/aarch32/jchuff-neon.c +334 -0
  125. data/ext/image_pack/vendor/mozjpeg/simd/arm/aarch32/jsimd.c +976 -0
  126. data/ext/image_pack/vendor/mozjpeg/simd/arm/aarch32/jsimd_neon.S +1200 -0
  127. data/ext/image_pack/vendor/mozjpeg/simd/arm/aarch64/jccolext-neon.c +316 -0
  128. data/ext/image_pack/vendor/mozjpeg/simd/arm/aarch64/jchuff-neon.c +411 -0
  129. data/ext/image_pack/vendor/mozjpeg/simd/arm/aarch64/jsimd.c +1053 -0
  130. data/ext/image_pack/vendor/mozjpeg/simd/arm/aarch64/jsimd_neon.S +2254 -0
  131. data/ext/image_pack/vendor/mozjpeg/simd/arm/align.h +28 -0
  132. data/ext/image_pack/vendor/mozjpeg/simd/arm/jccolor-neon.c +160 -0
  133. data/ext/image_pack/vendor/mozjpeg/simd/arm/jcgray-neon.c +120 -0
  134. data/ext/image_pack/vendor/mozjpeg/simd/arm/jcgryext-neon.c +106 -0
  135. data/ext/image_pack/vendor/mozjpeg/simd/arm/jchuff.h +131 -0
  136. data/ext/image_pack/vendor/mozjpeg/simd/arm/jcphuff-neon.c +623 -0
  137. data/ext/image_pack/vendor/mozjpeg/simd/arm/jcsample-neon.c +192 -0
  138. data/ext/image_pack/vendor/mozjpeg/simd/arm/jdcolext-neon.c +374 -0
  139. data/ext/image_pack/vendor/mozjpeg/simd/arm/jdcolor-neon.c +141 -0
  140. data/ext/image_pack/vendor/mozjpeg/simd/arm/jdmerge-neon.c +144 -0
  141. data/ext/image_pack/vendor/mozjpeg/simd/arm/jdmrgext-neon.c +723 -0
  142. data/ext/image_pack/vendor/mozjpeg/simd/arm/jdsample-neon.c +569 -0
  143. data/ext/image_pack/vendor/mozjpeg/simd/arm/jfdctfst-neon.c +214 -0
  144. data/ext/image_pack/vendor/mozjpeg/simd/arm/jfdctint-neon.c +376 -0
  145. data/ext/image_pack/vendor/mozjpeg/simd/arm/jidctfst-neon.c +472 -0
  146. data/ext/image_pack/vendor/mozjpeg/simd/arm/jidctint-neon.c +801 -0
  147. data/ext/image_pack/vendor/mozjpeg/simd/arm/jidctred-neon.c +486 -0
  148. data/ext/image_pack/vendor/mozjpeg/simd/arm/jquanti-neon.c +193 -0
  149. data/ext/image_pack/vendor/mozjpeg/simd/arm/neon-compat.h +26 -0
  150. data/ext/image_pack/vendor/mozjpeg/simd/arm/neon-compat.h.in +37 -0
  151. data/ext/image_pack/vendor/mozjpeg/simd/i386/jccolext-avx2.asm +578 -0
  152. data/ext/image_pack/vendor/mozjpeg/simd/i386/jccolext-mmx.asm +476 -0
  153. data/ext/image_pack/vendor/mozjpeg/simd/i386/jccolext-sse2.asm +503 -0
  154. data/ext/image_pack/vendor/mozjpeg/simd/i386/jccolor-avx2.asm +121 -0
  155. data/ext/image_pack/vendor/mozjpeg/simd/i386/jccolor-mmx.asm +121 -0
  156. data/ext/image_pack/vendor/mozjpeg/simd/i386/jccolor-sse2.asm +120 -0
  157. data/ext/image_pack/vendor/mozjpeg/simd/i386/jcgray-avx2.asm +113 -0
  158. data/ext/image_pack/vendor/mozjpeg/simd/i386/jcgray-mmx.asm +113 -0
  159. data/ext/image_pack/vendor/mozjpeg/simd/i386/jcgray-sse2.asm +112 -0
  160. data/ext/image_pack/vendor/mozjpeg/simd/i386/jcgryext-avx2.asm +457 -0
  161. data/ext/image_pack/vendor/mozjpeg/simd/i386/jcgryext-mmx.asm +355 -0
  162. data/ext/image_pack/vendor/mozjpeg/simd/i386/jcgryext-sse2.asm +382 -0
  163. data/ext/image_pack/vendor/mozjpeg/simd/i386/jchuff-sse2.asm +761 -0
  164. data/ext/image_pack/vendor/mozjpeg/simd/i386/jcphuff-sse2.asm +662 -0
  165. data/ext/image_pack/vendor/mozjpeg/simd/i386/jcsample-avx2.asm +388 -0
  166. data/ext/image_pack/vendor/mozjpeg/simd/i386/jcsample-mmx.asm +324 -0
  167. data/ext/image_pack/vendor/mozjpeg/simd/i386/jcsample-sse2.asm +351 -0
  168. data/ext/image_pack/vendor/mozjpeg/simd/i386/jdcolext-avx2.asm +515 -0
  169. data/ext/image_pack/vendor/mozjpeg/simd/i386/jdcolext-mmx.asm +404 -0
  170. data/ext/image_pack/vendor/mozjpeg/simd/i386/jdcolext-sse2.asm +458 -0
  171. data/ext/image_pack/vendor/mozjpeg/simd/i386/jdcolor-avx2.asm +118 -0
  172. data/ext/image_pack/vendor/mozjpeg/simd/i386/jdcolor-mmx.asm +117 -0
  173. data/ext/image_pack/vendor/mozjpeg/simd/i386/jdcolor-sse2.asm +117 -0
  174. data/ext/image_pack/vendor/mozjpeg/simd/i386/jdmerge-avx2.asm +136 -0
  175. data/ext/image_pack/vendor/mozjpeg/simd/i386/jdmerge-mmx.asm +123 -0
  176. data/ext/image_pack/vendor/mozjpeg/simd/i386/jdmerge-sse2.asm +135 -0
  177. data/ext/image_pack/vendor/mozjpeg/simd/i386/jdmrgext-avx2.asm +575 -0
  178. data/ext/image_pack/vendor/mozjpeg/simd/i386/jdmrgext-mmx.asm +460 -0
  179. data/ext/image_pack/vendor/mozjpeg/simd/i386/jdmrgext-sse2.asm +517 -0
  180. data/ext/image_pack/vendor/mozjpeg/simd/i386/jdsample-avx2.asm +760 -0
  181. data/ext/image_pack/vendor/mozjpeg/simd/i386/jdsample-mmx.asm +731 -0
  182. data/ext/image_pack/vendor/mozjpeg/simd/i386/jdsample-sse2.asm +724 -0
  183. data/ext/image_pack/vendor/mozjpeg/simd/i386/jfdctflt-3dn.asm +318 -0
  184. data/ext/image_pack/vendor/mozjpeg/simd/i386/jfdctflt-sse.asm +369 -0
  185. data/ext/image_pack/vendor/mozjpeg/simd/i386/jfdctfst-mmx.asm +395 -0
  186. data/ext/image_pack/vendor/mozjpeg/simd/i386/jfdctfst-sse2.asm +403 -0
  187. data/ext/image_pack/vendor/mozjpeg/simd/i386/jfdctint-avx2.asm +331 -0
  188. data/ext/image_pack/vendor/mozjpeg/simd/i386/jfdctint-mmx.asm +620 -0
  189. data/ext/image_pack/vendor/mozjpeg/simd/i386/jfdctint-sse2.asm +633 -0
  190. data/ext/image_pack/vendor/mozjpeg/simd/i386/jidctflt-3dn.asm +451 -0
  191. data/ext/image_pack/vendor/mozjpeg/simd/i386/jidctflt-sse.asm +571 -0
  192. data/ext/image_pack/vendor/mozjpeg/simd/i386/jidctflt-sse2.asm +497 -0
  193. data/ext/image_pack/vendor/mozjpeg/simd/i386/jidctfst-mmx.asm +499 -0
  194. data/ext/image_pack/vendor/mozjpeg/simd/i386/jidctfst-sse2.asm +501 -0
  195. data/ext/image_pack/vendor/mozjpeg/simd/i386/jidctint-avx2.asm +453 -0
  196. data/ext/image_pack/vendor/mozjpeg/simd/i386/jidctint-mmx.asm +851 -0
  197. data/ext/image_pack/vendor/mozjpeg/simd/i386/jidctint-sse2.asm +858 -0
  198. data/ext/image_pack/vendor/mozjpeg/simd/i386/jidctred-mmx.asm +704 -0
  199. data/ext/image_pack/vendor/mozjpeg/simd/i386/jidctred-sse2.asm +592 -0
  200. data/ext/image_pack/vendor/mozjpeg/simd/i386/jquant-3dn.asm +230 -0
  201. data/ext/image_pack/vendor/mozjpeg/simd/i386/jquant-mmx.asm +276 -0
  202. data/ext/image_pack/vendor/mozjpeg/simd/i386/jquant-sse.asm +208 -0
  203. data/ext/image_pack/vendor/mozjpeg/simd/i386/jquantf-sse2.asm +168 -0
  204. data/ext/image_pack/vendor/mozjpeg/simd/i386/jquanti-avx2.asm +188 -0
  205. data/ext/image_pack/vendor/mozjpeg/simd/i386/jquanti-sse2.asm +201 -0
  206. data/ext/image_pack/vendor/mozjpeg/simd/i386/jsimd.c +1312 -0
  207. data/ext/image_pack/vendor/mozjpeg/simd/i386/jsimdcpu.asm +135 -0
  208. data/ext/image_pack/vendor/mozjpeg/simd/jsimd.h +1258 -0
  209. data/ext/image_pack/vendor/mozjpeg/simd/mips/jsimd.c +1143 -0
  210. data/ext/image_pack/vendor/mozjpeg/simd/mips/jsimd_dspr2.S +4543 -0
  211. data/ext/image_pack/vendor/mozjpeg/simd/mips/jsimd_dspr2_asm.h +292 -0
  212. data/ext/image_pack/vendor/mozjpeg/simd/mips64/jccolext-mmi.c +455 -0
  213. data/ext/image_pack/vendor/mozjpeg/simd/mips64/jccolor-mmi.c +148 -0
  214. data/ext/image_pack/vendor/mozjpeg/simd/mips64/jcgray-mmi.c +132 -0
  215. data/ext/image_pack/vendor/mozjpeg/simd/mips64/jcgryext-mmi.c +374 -0
  216. data/ext/image_pack/vendor/mozjpeg/simd/mips64/jcsample-mmi.c +98 -0
  217. data/ext/image_pack/vendor/mozjpeg/simd/mips64/jcsample.h +28 -0
  218. data/ext/image_pack/vendor/mozjpeg/simd/mips64/jdcolext-mmi.c +415 -0
  219. data/ext/image_pack/vendor/mozjpeg/simd/mips64/jdcolor-mmi.c +139 -0
  220. data/ext/image_pack/vendor/mozjpeg/simd/mips64/jdmerge-mmi.c +149 -0
  221. data/ext/image_pack/vendor/mozjpeg/simd/mips64/jdmrgext-mmi.c +615 -0
  222. data/ext/image_pack/vendor/mozjpeg/simd/mips64/jdsample-mmi.c +304 -0
  223. data/ext/image_pack/vendor/mozjpeg/simd/mips64/jfdctfst-mmi.c +255 -0
  224. data/ext/image_pack/vendor/mozjpeg/simd/mips64/jfdctint-mmi.c +398 -0
  225. data/ext/image_pack/vendor/mozjpeg/simd/mips64/jidctfst-mmi.c +395 -0
  226. data/ext/image_pack/vendor/mozjpeg/simd/mips64/jidctint-mmi.c +571 -0
  227. data/ext/image_pack/vendor/mozjpeg/simd/mips64/jquanti-mmi.c +124 -0
  228. data/ext/image_pack/vendor/mozjpeg/simd/mips64/jsimd.c +866 -0
  229. data/ext/image_pack/vendor/mozjpeg/simd/mips64/jsimd_mmi.h +69 -0
  230. data/ext/image_pack/vendor/mozjpeg/simd/mips64/loongson-mmintrin.h +1334 -0
  231. data/ext/image_pack/vendor/mozjpeg/simd/nasm/jcolsamp.inc +135 -0
  232. data/ext/image_pack/vendor/mozjpeg/simd/nasm/jdct.inc +31 -0
  233. data/ext/image_pack/vendor/mozjpeg/simd/nasm/jsimdcfg.inc +93 -0
  234. data/ext/image_pack/vendor/mozjpeg/simd/nasm/jsimdcfg.inc.h +133 -0
  235. data/ext/image_pack/vendor/mozjpeg/simd/nasm/jsimdext.inc +520 -0
  236. data/ext/image_pack/vendor/mozjpeg/simd/powerpc/jccolext-altivec.c +269 -0
  237. data/ext/image_pack/vendor/mozjpeg/simd/powerpc/jccolor-altivec.c +116 -0
  238. data/ext/image_pack/vendor/mozjpeg/simd/powerpc/jcgray-altivec.c +111 -0
  239. data/ext/image_pack/vendor/mozjpeg/simd/powerpc/jcgryext-altivec.c +228 -0
  240. data/ext/image_pack/vendor/mozjpeg/simd/powerpc/jcsample-altivec.c +159 -0
  241. data/ext/image_pack/vendor/mozjpeg/simd/powerpc/jcsample.h +28 -0
  242. data/ext/image_pack/vendor/mozjpeg/simd/powerpc/jdcolext-altivec.c +276 -0
  243. data/ext/image_pack/vendor/mozjpeg/simd/powerpc/jdcolor-altivec.c +106 -0
  244. data/ext/image_pack/vendor/mozjpeg/simd/powerpc/jdmerge-altivec.c +130 -0
  245. data/ext/image_pack/vendor/mozjpeg/simd/powerpc/jdmrgext-altivec.c +329 -0
  246. data/ext/image_pack/vendor/mozjpeg/simd/powerpc/jdsample-altivec.c +400 -0
  247. data/ext/image_pack/vendor/mozjpeg/simd/powerpc/jfdctfst-altivec.c +154 -0
  248. data/ext/image_pack/vendor/mozjpeg/simd/powerpc/jfdctint-altivec.c +258 -0
  249. data/ext/image_pack/vendor/mozjpeg/simd/powerpc/jidctfst-altivec.c +255 -0
  250. data/ext/image_pack/vendor/mozjpeg/simd/powerpc/jidctint-altivec.c +357 -0
  251. data/ext/image_pack/vendor/mozjpeg/simd/powerpc/jquanti-altivec.c +250 -0
  252. data/ext/image_pack/vendor/mozjpeg/simd/powerpc/jsimd.c +884 -0
  253. data/ext/image_pack/vendor/mozjpeg/simd/powerpc/jsimd_altivec.h +98 -0
  254. data/ext/image_pack/vendor/mozjpeg/simd/x86_64/jccolext-avx2.asm +559 -0
  255. data/ext/image_pack/vendor/mozjpeg/simd/x86_64/jccolext-sse2.asm +484 -0
  256. data/ext/image_pack/vendor/mozjpeg/simd/x86_64/jccolor-avx2.asm +121 -0
  257. data/ext/image_pack/vendor/mozjpeg/simd/x86_64/jccolor-sse2.asm +120 -0
  258. data/ext/image_pack/vendor/mozjpeg/simd/x86_64/jcgray-avx2.asm +113 -0
  259. data/ext/image_pack/vendor/mozjpeg/simd/x86_64/jcgray-sse2.asm +112 -0
  260. data/ext/image_pack/vendor/mozjpeg/simd/x86_64/jcgryext-avx2.asm +438 -0
  261. data/ext/image_pack/vendor/mozjpeg/simd/x86_64/jcgryext-sse2.asm +363 -0
  262. data/ext/image_pack/vendor/mozjpeg/simd/x86_64/jchuff-sse2.asm +583 -0
  263. data/ext/image_pack/vendor/mozjpeg/simd/x86_64/jcphuff-sse2.asm +639 -0
  264. data/ext/image_pack/vendor/mozjpeg/simd/x86_64/jcsample-avx2.asm +367 -0
  265. data/ext/image_pack/vendor/mozjpeg/simd/x86_64/jcsample-sse2.asm +330 -0
  266. data/ext/image_pack/vendor/mozjpeg/simd/x86_64/jdcolext-avx2.asm +496 -0
  267. data/ext/image_pack/vendor/mozjpeg/simd/x86_64/jdcolext-sse2.asm +439 -0
  268. data/ext/image_pack/vendor/mozjpeg/simd/x86_64/jdcolor-avx2.asm +118 -0
  269. data/ext/image_pack/vendor/mozjpeg/simd/x86_64/jdcolor-sse2.asm +117 -0
  270. data/ext/image_pack/vendor/mozjpeg/simd/x86_64/jdmerge-avx2.asm +136 -0
  271. data/ext/image_pack/vendor/mozjpeg/simd/x86_64/jdmerge-sse2.asm +135 -0
  272. data/ext/image_pack/vendor/mozjpeg/simd/x86_64/jdmrgext-avx2.asm +596 -0
  273. data/ext/image_pack/vendor/mozjpeg/simd/x86_64/jdmrgext-sse2.asm +538 -0
  274. data/ext/image_pack/vendor/mozjpeg/simd/x86_64/jdsample-avx2.asm +696 -0
  275. data/ext/image_pack/vendor/mozjpeg/simd/x86_64/jdsample-sse2.asm +665 -0
  276. data/ext/image_pack/vendor/mozjpeg/simd/x86_64/jfdctflt-sse.asm +355 -0
  277. data/ext/image_pack/vendor/mozjpeg/simd/x86_64/jfdctfst-sse2.asm +389 -0
  278. data/ext/image_pack/vendor/mozjpeg/simd/x86_64/jfdctint-avx2.asm +320 -0
  279. data/ext/image_pack/vendor/mozjpeg/simd/x86_64/jfdctint-sse2.asm +619 -0
  280. data/ext/image_pack/vendor/mozjpeg/simd/x86_64/jidctflt-sse2.asm +482 -0
  281. data/ext/image_pack/vendor/mozjpeg/simd/x86_64/jidctfst-sse2.asm +491 -0
  282. data/ext/image_pack/vendor/mozjpeg/simd/x86_64/jidctint-avx2.asm +418 -0
  283. data/ext/image_pack/vendor/mozjpeg/simd/x86_64/jidctint-sse2.asm +847 -0
  284. data/ext/image_pack/vendor/mozjpeg/simd/x86_64/jidctred-sse2.asm +574 -0
  285. data/ext/image_pack/vendor/mozjpeg/simd/x86_64/jquantf-sse2.asm +155 -0
  286. data/ext/image_pack/vendor/mozjpeg/simd/x86_64/jquanti-avx2.asm +163 -0
  287. data/ext/image_pack/vendor/mozjpeg/simd/x86_64/jquanti-sse2.asm +188 -0
  288. data/ext/image_pack/vendor/mozjpeg/simd/x86_64/jsimd.c +1110 -0
  289. data/ext/image_pack/vendor/mozjpeg/simd/x86_64/jsimdcpu.asm +86 -0
  290. data/ext/image_pack/vendor/mozjpeg/strtest.c +170 -0
  291. data/ext/image_pack/vendor/mozjpeg/structure.txt +900 -0
  292. data/ext/image_pack/vendor/mozjpeg/tjbench.c +1044 -0
  293. data/ext/image_pack/vendor/mozjpeg/tjbenchtest.in +256 -0
  294. data/ext/image_pack/vendor/mozjpeg/tjbenchtest.java.in +215 -0
  295. data/ext/image_pack/vendor/mozjpeg/tjexample.c +406 -0
  296. data/ext/image_pack/vendor/mozjpeg/tjexampletest.in +149 -0
  297. data/ext/image_pack/vendor/mozjpeg/tjexampletest.java.in +151 -0
  298. data/ext/image_pack/vendor/mozjpeg/tjunittest.c +961 -0
  299. data/ext/image_pack/vendor/mozjpeg/tjutil.c +70 -0
  300. data/ext/image_pack/vendor/mozjpeg/tjutil.h +53 -0
  301. data/ext/image_pack/vendor/mozjpeg/transupp.c +2373 -0
  302. data/ext/image_pack/vendor/mozjpeg/transupp.h +243 -0
  303. data/ext/image_pack/vendor/mozjpeg/turbojpeg-jni.c +1259 -0
  304. data/ext/image_pack/vendor/mozjpeg/turbojpeg.c +2320 -0
  305. data/ext/image_pack/vendor/mozjpeg/turbojpeg.h +1784 -0
  306. data/ext/image_pack/vendor/mozjpeg/usage.txt +679 -0
  307. data/ext/image_pack/vendor/mozjpeg/wizard.txt +220 -0
  308. data/ext/image_pack/vendor/mozjpeg/wrbmp.c +552 -0
  309. data/ext/image_pack/vendor/mozjpeg/wrgif.c +580 -0
  310. data/ext/image_pack/vendor/mozjpeg/wrjpgcom.c +577 -0
  311. data/ext/image_pack/vendor/mozjpeg/wrppm.c +366 -0
  312. data/ext/image_pack/vendor/mozjpeg/wrtarga.c +258 -0
  313. data/ext/image_pack/vendor/mozjpeg/yuvjpeg.c +268 -0
  314. data/lib/image_pack/backend.rb +8 -0
  315. data/lib/image_pack/configuration.rb +23 -0
  316. data/lib/image_pack/errors.rb +13 -0
  317. data/lib/image_pack/version.rb +5 -0
  318. data/lib/image_pack.rb +208 -0
  319. metadata +433 -0
@@ -0,0 +1,1618 @@
1
+ #include <ruby.h>
2
+ #include <ruby/thread.h>
3
+ #include <ruby/version.h>
4
+ #include <ruby/encoding.h>
5
+
6
+ #if RUBY_API_VERSION_MAJOR < 3 || (RUBY_API_VERSION_MAJOR == 3 && RUBY_API_VERSION_MINOR < 4)
7
+ #error "image_pack requires Ruby 3.4+ (RB_NOGVL_OFFLOAD_SAFE not available)"
8
+ #endif
9
+
10
+ #include <errno.h>
11
+ #include <setjmp.h>
12
+ #include <stdatomic.h>
13
+ #include <stdint.h>
14
+ #include <limits.h>
15
+ #include <stdio.h>
16
+ #include <stdlib.h>
17
+ #include <string.h>
18
+ #include <jpeglib.h>
19
+
20
+ #ifndef IMAGE_PACK_INIT_EXPORT
21
+ #if defined(_WIN32)
22
+ #define IMAGE_PACK_INIT_EXPORT __declspec(dllexport)
23
+ #elif defined(__GNUC__) || defined(__clang__)
24
+ #define IMAGE_PACK_INIT_EXPORT __attribute__((visibility("default")))
25
+ #else
26
+ #define IMAGE_PACK_INIT_EXPORT
27
+ #endif
28
+ #endif
29
+
30
+ #ifndef RB_NOGVL_OFFLOAD_SAFE
31
+ #error "RB_NOGVL_OFFLOAD_SAFE is required by image_pack"
32
+ #endif
33
+
34
+ #ifndef TRUE
35
+ #define TRUE 1
36
+ #endif
37
+
38
+ #ifndef FALSE
39
+ #define FALSE 0
40
+ #endif
41
+
42
+ typedef enum { IP_ALGO_JPEG_TURBO = 1, IP_ALGO_MOZJPEG = 2 } ip_algo_t;
43
+
44
+ typedef enum {
45
+ IP_EXEC_DIRECT = 1,
46
+ IP_EXEC_NOGVL = 2,
47
+ IP_EXEC_OFFLOAD = 3,
48
+ IP_EXEC_AUTO = 4
49
+ } ip_execution_t;
50
+
51
+ typedef enum { IP_INPUT_BYTES = 1, IP_INPUT_PATH = 2, IP_INPUT_IO_BUFFER = 3 } ip_input_kind_t;
52
+
53
+ typedef enum { IP_OUTPUT_RETURN_STRING = 1, IP_OUTPUT_PATH = 2 } ip_output_kind_t;
54
+
55
+ typedef enum {
56
+ IP_OK = 0,
57
+ IP_ERR_INVALID_ARGUMENT,
58
+ IP_ERR_INVALID_IMAGE,
59
+ IP_ERR_UNSUPPORTED,
60
+ IP_ERR_LIMIT,
61
+ IP_ERR_ENCODE,
62
+ IP_ERR_QUALITY,
63
+ IP_ERR_OOM,
64
+ IP_ERR_CANCELLED
65
+ } ip_status_t;
66
+
67
+ typedef enum { IP_OUTPUT_OWNER_NONE = 0, IP_OUTPUT_OWNER_MALLOC = 1 } ip_output_owner_t;
68
+
69
+ typedef struct {
70
+ const unsigned char *input_data;
71
+ size_t input_size;
72
+ unsigned char *owned_input_data;
73
+
74
+ const unsigned char *pixel_data;
75
+ size_t pixel_size;
76
+ unsigned char *owned_pixel_data;
77
+ int width;
78
+ int height;
79
+ int channels;
80
+ int bit_depth;
81
+ size_t decoded_bytes;
82
+
83
+ unsigned char *output_data;
84
+ size_t output_size;
85
+ size_t output_capacity;
86
+ ip_output_owner_t output_owner;
87
+ char *output_path;
88
+
89
+ int quality;
90
+ int ssim_guard_enabled;
91
+ double min_ssim;
92
+ double measured_ssim;
93
+ int selected_quality;
94
+ int progressive;
95
+ int strip_metadata;
96
+ int mozjpeg_trellis_enabled;
97
+ ip_algo_t algo;
98
+ ip_execution_t requested_execution;
99
+ ip_execution_t resolved_execution;
100
+ int has_scheduler;
101
+
102
+ size_t direct_input_threshold;
103
+ size_t direct_pixel_threshold;
104
+ uint64_t max_pixels;
105
+ int max_width;
106
+ int max_height;
107
+ size_t max_output_size;
108
+
109
+ ip_status_t status;
110
+ char error_message[512];
111
+
112
+ atomic_int cancelled;
113
+ int cancellable_requested;
114
+
115
+ jmp_buf jmpbuf;
116
+ int jmp_armed;
117
+
118
+ unsigned char *scratch_row;
119
+ size_t scratch_row_size;
120
+ } ip_context_t;
121
+
122
+ typedef struct {
123
+ struct jpeg_error_mgr pub;
124
+ ip_context_t *ctx;
125
+ } ip_jpeg_error_mgr;
126
+
127
+ static VALUE rb_mImagePack;
128
+ static VALUE rb_eImagePackError;
129
+ static VALUE rb_eImagePackInvalidArgumentError;
130
+ static VALUE rb_eImagePackInvalidImageError;
131
+ static VALUE rb_eImagePackUnsupportedError;
132
+ static VALUE rb_eImagePackLimitExceededError;
133
+ static VALUE rb_eImagePackEncodeError;
134
+ static VALUE rb_eImagePackQualityConstraintError;
135
+ static VALUE rb_eImagePackOutOfMemoryError;
136
+ static VALUE rb_eImagePackCancelledError;
137
+
138
+ static ID id_jpeg_turbo;
139
+ static ID id_mozjpeg;
140
+ static ID id_direct;
141
+ static ID id_nogvl;
142
+ static ID id_offload;
143
+ static ID id_auto;
144
+ static ID id_bytes;
145
+ static ID id_path;
146
+ static ID id_io_buffer;
147
+ static ID id_return_string;
148
+ static ID id_configuration;
149
+ static ID id_direct_input_threshold;
150
+ static ID id_direct_pixel_threshold;
151
+ static ID id_max_pixels;
152
+ static ID id_max_width;
153
+ static ID id_max_height;
154
+ static ID id_max_output_size;
155
+
156
+ static ip_context_t *ip_context_new(void);
157
+ static void ip_context_free(ip_context_t *ctx);
158
+ static void ip_context_set_error(ip_context_t *ctx, ip_status_t status, const char *message);
159
+
160
+ static VALUE ip_status_to_exception(ip_status_t status);
161
+ static void ip_raise_for_status(ip_context_t *ctx);
162
+ static int ip_checked_mul_size(size_t a, size_t b, size_t *out);
163
+ static int ip_checked_image_size(int width, int height, int channels, size_t *out);
164
+ static void ip_validate_quality_or_raise(ip_context_t *ctx);
165
+ static void ip_validate_min_ssim_or_raise(ip_context_t *ctx);
166
+ static int ip_bool_value(VALUE value);
167
+
168
+ static ip_algo_t ip_parse_algo(VALUE sym);
169
+ static ip_execution_t ip_parse_execution(VALUE sym);
170
+ static ip_input_kind_t ip_parse_input_kind(VALUE sym);
171
+ static ip_output_kind_t ip_parse_output_kind(VALUE sym);
172
+
173
+ static int ip_prepare_input_bytes(ip_context_t *ctx, VALUE input, ip_input_kind_t kind);
174
+ static int ip_prepare_pixels(ip_context_t *ctx, VALUE buffer, int width, int height, int channels);
175
+ static int ip_prepare_output_path(ip_context_t *ctx, VALUE output, ip_output_kind_t kind);
176
+ static VALUE ip_finish_output(ip_context_t *ctx, ip_output_kind_t kind);
177
+
178
+ static int ip_inspect_jpeg_header(ip_context_t *ctx);
179
+ static VALUE ip_inspect_image_entry(VALUE self, VALUE input, VALUE input_kind);
180
+
181
+ static void ip_resolve_execution(ip_context_t *ctx);
182
+ static void ip_unblock_function(void *data);
183
+ static void *ip_run_encode_nogvl(void *data);
184
+ static int ip_run_context(ip_context_t *ctx);
185
+
186
+ static void validate_limits_for_pixels(ip_context_t *ctx);
187
+
188
+ static int ip_jpeg_decode_to_pixels(ip_context_t *ctx, unsigned char **pixels, int *width,
189
+ int *height, int *channels, int fast_decode_mode);
190
+ static int guarded_compress_jpeg_input_with_mode(ip_context_t *ctx, int mozjpeg_size_mode);
191
+ static int ip_jpeg_turbo_compress(ip_context_t *ctx);
192
+ static int ip_mozjpeg_compress(ip_context_t *ctx);
193
+
194
+ #if defined(IMAGE_PACK_ENABLE_LOSSLESS_TRANSCODE_FAST_PATH)
195
+ static int ip_jpeg_transcode_coefficients(ip_context_t *ctx, int mozjpeg_size_mode);
196
+ #endif
197
+
198
+ static VALUE ip_compress_jpeg_entry(VALUE self, VALUE input, VALUE input_kind, VALUE output,
199
+ VALUE output_kind, VALUE algo, VALUE quality, VALUE min_ssim,
200
+ VALUE mozjpeg_trellis, VALUE progressive, VALUE strip_metadata,
201
+ VALUE execution, VALUE cancellable, VALUE has_scheduler);
202
+ static VALUE ip_compress_pixels_entry(VALUE self, VALUE buffer, VALUE width, VALUE height,
203
+ VALUE channels, VALUE output, VALUE output_kind, VALUE algo,
204
+ VALUE quality, VALUE progressive, VALUE execution,
205
+ VALUE cancellable, VALUE has_scheduler);
206
+
207
+ static VALUE ip_status_to_exception(ip_status_t status) {
208
+ switch (status) {
209
+ case IP_ERR_INVALID_ARGUMENT:
210
+ return rb_eImagePackInvalidArgumentError;
211
+ case IP_ERR_INVALID_IMAGE:
212
+ return rb_eImagePackInvalidImageError;
213
+ case IP_ERR_UNSUPPORTED:
214
+ return rb_eImagePackUnsupportedError;
215
+ case IP_ERR_LIMIT:
216
+ return rb_eImagePackLimitExceededError;
217
+ case IP_ERR_QUALITY:
218
+ return rb_eImagePackQualityConstraintError;
219
+ case IP_ERR_OOM:
220
+ return rb_eImagePackOutOfMemoryError;
221
+ case IP_ERR_CANCELLED:
222
+ return rb_eImagePackCancelledError;
223
+ case IP_ERR_ENCODE:
224
+ default:
225
+ return rb_eImagePackEncodeError;
226
+ }
227
+ }
228
+
229
+ static void ip_raise_for_status(ip_context_t *ctx) {
230
+ if (!ctx || ctx->status == IP_OK)
231
+ return;
232
+
233
+ const char *message = ctx->error_message[0] ? ctx->error_message : "image_pack native error";
234
+ rb_raise(ip_status_to_exception(ctx->status), "%s", message);
235
+ }
236
+
237
+ static int ip_checked_mul_size(size_t a, size_t b, size_t *out) {
238
+ if (a != 0 && b > SIZE_MAX / a)
239
+ return 0;
240
+ *out = a * b;
241
+ return 1;
242
+ }
243
+
244
+ static int ip_checked_image_size(int width, int height, int channels, size_t *out) {
245
+ size_t pixels = 0;
246
+
247
+ if (width <= 0 || height <= 0 || channels <= 0)
248
+ return 0;
249
+ if (!ip_checked_mul_size((size_t)width, (size_t)height, &pixels))
250
+ return 0;
251
+ return ip_checked_mul_size(pixels, (size_t)channels, out);
252
+ }
253
+
254
+ static void ip_validate_quality_or_raise(ip_context_t *ctx) {
255
+ if (ctx->quality >= 1 && ctx->quality <= 100)
256
+ return;
257
+
258
+ int quality = ctx->quality;
259
+ ip_context_free(ctx);
260
+ rb_raise(rb_eImagePackInvalidArgumentError, "quality must be Integer 1..100, got: %d", quality);
261
+ }
262
+
263
+ static void ip_validate_min_ssim_or_raise(ip_context_t *ctx) {
264
+ if (!ctx->ssim_guard_enabled)
265
+ return;
266
+ if (ctx->min_ssim > 0.0 && ctx->min_ssim <= 1.0)
267
+ return;
268
+
269
+ double min_ssim = ctx->min_ssim;
270
+ ip_context_free(ctx);
271
+ rb_raise(rb_eImagePackInvalidArgumentError,
272
+ "min_ssim must be Numeric > 0.0 and <= 1.0, got: %.17g", min_ssim);
273
+ }
274
+
275
+ static int ip_bool_value(VALUE value) {
276
+ if (NIL_P(value) || value == Qfalse)
277
+ return 0;
278
+ if (RB_INTEGER_TYPE_P(value))
279
+ return NUM2INT(value) != 0;
280
+ return 1;
281
+ }
282
+
283
+ static ip_context_t *ip_context_new(void) {
284
+ ip_context_t *ctx = (ip_context_t *)calloc(1, sizeof(ip_context_t));
285
+ if (!ctx)
286
+ return NULL;
287
+
288
+ ctx->status = IP_OK;
289
+ ctx->quality = 82;
290
+ ctx->mozjpeg_trellis_enabled = 1;
291
+ ctx->selected_quality = 82;
292
+ ctx->requested_execution = IP_EXEC_AUTO;
293
+ ctx->resolved_execution = IP_EXEC_AUTO;
294
+ ctx->direct_input_threshold = 128 * 1024;
295
+ ctx->direct_pixel_threshold = 1024 * 1024;
296
+ ctx->max_pixels = 100000000ULL;
297
+ ctx->max_width = 30000;
298
+ ctx->max_height = 30000;
299
+ ctx->max_output_size = 256 * 1024 * 1024;
300
+ atomic_init(&ctx->cancelled, 0);
301
+ return ctx;
302
+ }
303
+
304
+ static void ip_context_free(ip_context_t *ctx) {
305
+ if (!ctx)
306
+ return;
307
+
308
+ free(ctx->owned_input_data);
309
+ free(ctx->owned_pixel_data);
310
+ free(ctx->output_path);
311
+ free(ctx->scratch_row);
312
+
313
+ if (ctx->output_data && ctx->output_owner == IP_OUTPUT_OWNER_MALLOC) {
314
+ free(ctx->output_data);
315
+ }
316
+
317
+ free(ctx);
318
+ }
319
+
320
+ static void ip_context_set_error(ip_context_t *ctx, ip_status_t status, const char *message) {
321
+ if (!ctx)
322
+ return;
323
+ ctx->status = status;
324
+ if (!message)
325
+ message = "unknown image_pack native error";
326
+ snprintf(ctx->error_message, sizeof(ctx->error_message), "%s", message);
327
+ }
328
+
329
+ static ID symbol_id(VALUE sym, const char *kind) {
330
+ if (!RB_TYPE_P(sym, T_SYMBOL)) {
331
+ rb_raise(rb_eImagePackInvalidArgumentError, "%s must be a Symbol", kind);
332
+ }
333
+
334
+ return SYM2ID(sym);
335
+ }
336
+
337
+ static ip_algo_t ip_parse_algo(VALUE sym) {
338
+ ID id = symbol_id(sym, "algo");
339
+ if (id == id_jpeg_turbo)
340
+ return IP_ALGO_JPEG_TURBO;
341
+ if (id == id_mozjpeg)
342
+ return IP_ALGO_MOZJPEG;
343
+ rb_raise(rb_eImagePackInvalidArgumentError, "unknown algo");
344
+ }
345
+
346
+ static ip_execution_t ip_parse_execution(VALUE sym) {
347
+ ID id = symbol_id(sym, "execution");
348
+ if (id == id_direct)
349
+ return IP_EXEC_DIRECT;
350
+ if (id == id_nogvl)
351
+ return IP_EXEC_NOGVL;
352
+ if (id == id_offload)
353
+ return IP_EXEC_OFFLOAD;
354
+ if (id == id_auto)
355
+ return IP_EXEC_AUTO;
356
+ rb_raise(rb_eImagePackInvalidArgumentError, "unknown execution");
357
+ }
358
+
359
+ static ip_input_kind_t ip_parse_input_kind(VALUE sym) {
360
+ ID id = symbol_id(sym, "input kind");
361
+ if (id == id_bytes)
362
+ return IP_INPUT_BYTES;
363
+ if (id == id_path)
364
+ return IP_INPUT_PATH;
365
+ if (id == id_io_buffer)
366
+ return IP_INPUT_IO_BUFFER;
367
+ rb_raise(rb_eImagePackInvalidArgumentError, "unknown input kind");
368
+ }
369
+
370
+ static ip_output_kind_t ip_parse_output_kind(VALUE sym) {
371
+ ID id = symbol_id(sym, "output kind");
372
+ if (id == id_return_string)
373
+ return IP_OUTPUT_RETURN_STRING;
374
+ if (id == id_path)
375
+ return IP_OUTPUT_PATH;
376
+ rb_raise(rb_eImagePackInvalidArgumentError, "unknown output kind");
377
+ }
378
+
379
+ static VALUE pathname_to_s(VALUE object) {
380
+ if (RB_TYPE_P(object, T_STRING))
381
+ return object;
382
+ return rb_funcall(object, rb_intern("to_s"), 0);
383
+ }
384
+
385
+ static int read_file_to_owned_buffer(ip_context_t *ctx, const char *path) {
386
+ FILE *fp = fopen(path, "rb");
387
+ if (!fp) {
388
+ char message[512];
389
+ snprintf(message, sizeof(message), "failed to open input path: %s", path);
390
+ ip_context_set_error(ctx, IP_ERR_INVALID_ARGUMENT, message);
391
+ return 0;
392
+ }
393
+
394
+ if (fseek(fp, 0, SEEK_END) != 0) {
395
+ fclose(fp);
396
+ ip_context_set_error(ctx, IP_ERR_INVALID_ARGUMENT, "failed to seek input path");
397
+ return 0;
398
+ }
399
+
400
+ long size = ftell(fp);
401
+ if (size < 0) {
402
+ fclose(fp);
403
+ ip_context_set_error(ctx, IP_ERR_INVALID_ARGUMENT, "failed to determine input size");
404
+ return 0;
405
+ }
406
+ rewind(fp);
407
+
408
+ unsigned char *data = (unsigned char *)malloc((size_t)size);
409
+ if (!data && size > 0) {
410
+ fclose(fp);
411
+ ip_context_set_error(ctx, IP_ERR_OOM, "failed to allocate input file buffer");
412
+ return 0;
413
+ }
414
+
415
+ size_t read_size = fread(data, 1, (size_t)size, fp);
416
+ fclose(fp);
417
+ if (read_size != (size_t)size) {
418
+ free(data);
419
+ ip_context_set_error(ctx, IP_ERR_INVALID_ARGUMENT, "failed to read input path");
420
+ return 0;
421
+ }
422
+
423
+ ctx->owned_input_data = data;
424
+ ctx->input_data = data;
425
+ ctx->input_size = read_size;
426
+ return 1;
427
+ }
428
+
429
+ static VALUE io_buffer_to_string(VALUE buffer) {
430
+ VALUE size = rb_funcall(buffer, rb_intern("size"), 0);
431
+ return rb_funcall(buffer, rb_intern("get_string"), 2, LONG2NUM(0), size);
432
+ }
433
+
434
+ static int ip_prepare_input_bytes(ip_context_t *ctx, VALUE input, ip_input_kind_t kind) {
435
+ if (kind == IP_INPUT_BYTES) {
436
+ Check_Type(input, T_STRING);
437
+ size_t len = (size_t)RSTRING_LEN(input);
438
+ unsigned char *copy = (unsigned char *)malloc(len);
439
+ if (!copy && len > 0) {
440
+ ip_context_set_error(ctx, IP_ERR_OOM, "failed to copy binary String input");
441
+ return 0;
442
+ }
443
+ if (len > 0)
444
+ memcpy(copy, RSTRING_PTR(input), len);
445
+ ctx->owned_input_data = copy;
446
+ ctx->input_data = copy;
447
+ ctx->input_size = len;
448
+ return 1;
449
+ }
450
+
451
+ if (kind == IP_INPUT_IO_BUFFER) {
452
+ VALUE str = io_buffer_to_string(input);
453
+ StringValue(str);
454
+ size_t len = (size_t)RSTRING_LEN(str);
455
+ unsigned char *copy = (unsigned char *)malloc(len);
456
+ if (!copy && len > 0) {
457
+ ip_context_set_error(ctx, IP_ERR_OOM, "failed to copy IO::Buffer input");
458
+ return 0;
459
+ }
460
+ if (len > 0)
461
+ memcpy(copy, RSTRING_PTR(str), len);
462
+ ctx->owned_input_data = copy;
463
+ ctx->input_data = copy;
464
+ ctx->input_size = len;
465
+ return 1;
466
+ }
467
+
468
+ VALUE path_value = pathname_to_s(input);
469
+ StringValue(path_value);
470
+ return read_file_to_owned_buffer(ctx, StringValueCStr(path_value));
471
+ }
472
+
473
+ static int ip_prepare_pixels(ip_context_t *ctx, VALUE buffer, int width, int height, int channels) {
474
+ VALUE str;
475
+ if (RB_TYPE_P(buffer, T_STRING)) {
476
+ str = buffer;
477
+ } else {
478
+ str = io_buffer_to_string(buffer);
479
+ }
480
+
481
+ StringValue(str);
482
+ size_t expected = 0;
483
+ if (!ip_checked_image_size(width, height, channels, &expected)) {
484
+ ip_context_set_error(ctx, IP_ERR_INVALID_ARGUMENT,
485
+ "width * height * channels overflows native size");
486
+ return 0;
487
+ }
488
+
489
+ if ((size_t)RSTRING_LEN(str) < expected) {
490
+ ip_context_set_error(ctx, IP_ERR_INVALID_ARGUMENT,
491
+ "pixel buffer is smaller than width * height * channels");
492
+ return 0;
493
+ }
494
+
495
+ unsigned char *copy = (unsigned char *)malloc(expected);
496
+ if (!copy && expected > 0) {
497
+ ip_context_set_error(ctx, IP_ERR_OOM, "failed to copy pixel buffer");
498
+ return 0;
499
+ }
500
+
501
+ if (expected > 0)
502
+ memcpy(copy, RSTRING_PTR(str), expected);
503
+ ctx->owned_pixel_data = copy;
504
+ ctx->pixel_data = copy;
505
+ ctx->pixel_size = expected;
506
+ ctx->width = width;
507
+ ctx->height = height;
508
+ ctx->channels = channels;
509
+ ctx->bit_depth = 8;
510
+ ctx->decoded_bytes = expected;
511
+ return 1;
512
+ }
513
+
514
+ static int ip_prepare_output_path(ip_context_t *ctx, VALUE output, ip_output_kind_t kind) {
515
+ if (kind == IP_OUTPUT_RETURN_STRING)
516
+ return 1;
517
+
518
+ VALUE path_value = pathname_to_s(output);
519
+ StringValue(path_value);
520
+ const char *path = StringValueCStr(path_value);
521
+ ctx->output_path = strdup(path);
522
+ if (!ctx->output_path) {
523
+ ip_context_set_error(ctx, IP_ERR_OOM, "failed to copy output path");
524
+ return 0;
525
+ }
526
+ return 1;
527
+ }
528
+
529
+ static VALUE ip_finish_output(ip_context_t *ctx, ip_output_kind_t kind) {
530
+ ip_raise_for_status(ctx);
531
+
532
+ if (kind == IP_OUTPUT_RETURN_STRING) {
533
+ VALUE out = rb_str_new((const char *)ctx->output_data, (long)ctx->output_size);
534
+ rb_enc_associate(out, rb_ascii8bit_encoding());
535
+ return out;
536
+ }
537
+
538
+ FILE *fp = fopen(ctx->output_path, "wb");
539
+ if (!fp) {
540
+ rb_raise(rb_eImagePackInvalidArgumentError, "failed to open output path: %s",
541
+ ctx->output_path);
542
+ }
543
+
544
+ size_t written = fwrite(ctx->output_data, 1, ctx->output_size, fp);
545
+ fclose(fp);
546
+ if (written != ctx->output_size) {
547
+ rb_raise(rb_eImagePackEncodeError, "failed to write full JPEG output");
548
+ }
549
+
550
+ return Qtrue;
551
+ }
552
+
553
+ static void ip_jpeg_invalid_error_exit(j_common_ptr cinfo) {
554
+ ip_jpeg_error_mgr *err = (ip_jpeg_error_mgr *)cinfo->err;
555
+ char buffer[JMSG_LENGTH_MAX];
556
+ (*cinfo->err->format_message)(cinfo, buffer);
557
+ ip_context_set_error(err->ctx, IP_ERR_INVALID_IMAGE, buffer);
558
+ if (err->ctx->jmp_armed)
559
+ longjmp(err->ctx->jmpbuf, 1);
560
+ }
561
+
562
+ static void ip_jpeg_encode_error_exit(j_common_ptr cinfo) {
563
+ ip_jpeg_error_mgr *err = (ip_jpeg_error_mgr *)cinfo->err;
564
+ char buffer[JMSG_LENGTH_MAX];
565
+ (*cinfo->err->format_message)(cinfo, buffer);
566
+ ip_context_set_error(err->ctx, IP_ERR_ENCODE, buffer);
567
+ if (err->ctx->jmp_armed)
568
+ longjmp(err->ctx->jmpbuf, 1);
569
+ }
570
+
571
+ static int ip_inspect_jpeg_header(ip_context_t *ctx) {
572
+ if (!ctx->input_data || ctx->input_size < 2 || ctx->input_data[0] != 0xFF ||
573
+ ctx->input_data[1] != 0xD8) {
574
+ ip_context_set_error(ctx, IP_ERR_INVALID_IMAGE, "input is not a JPEG image");
575
+ return 0;
576
+ }
577
+
578
+ struct jpeg_decompress_struct cinfo;
579
+ ip_jpeg_error_mgr jerr;
580
+ memset(&cinfo, 0, sizeof(cinfo));
581
+ memset(&jerr, 0, sizeof(jerr));
582
+
583
+ cinfo.err = jpeg_std_error(&jerr.pub);
584
+ jerr.pub.error_exit = ip_jpeg_invalid_error_exit;
585
+ jerr.ctx = ctx;
586
+
587
+ ctx->jmp_armed = 1;
588
+ if (setjmp(ctx->jmpbuf)) {
589
+ ctx->jmp_armed = 0;
590
+ jpeg_destroy_decompress(&cinfo);
591
+ return 0;
592
+ }
593
+
594
+ jpeg_create_decompress(&cinfo);
595
+ jpeg_mem_src(&cinfo, ctx->input_data, (unsigned long)ctx->input_size);
596
+ int rc = jpeg_read_header(&cinfo, TRUE);
597
+ if (rc != JPEG_HEADER_OK) {
598
+ jpeg_destroy_decompress(&cinfo);
599
+ ctx->jmp_armed = 0;
600
+ ip_context_set_error(ctx, IP_ERR_INVALID_IMAGE, "invalid JPEG header");
601
+ return 0;
602
+ }
603
+
604
+ ctx->width = (int)cinfo.image_width;
605
+ ctx->height = (int)cinfo.image_height;
606
+ ctx->channels = cinfo.num_components;
607
+ if (ctx->channels != 1 && ctx->channels != 3 && ctx->channels != 4) {
608
+ ctx->channels = 3;
609
+ }
610
+ ctx->bit_depth = 8;
611
+ ctx->decoded_bytes = (size_t)ctx->width * (size_t)ctx->height * (size_t)ctx->channels;
612
+
613
+ jpeg_destroy_decompress(&cinfo);
614
+ ctx->jmp_armed = 0;
615
+
616
+ if (ctx->width <= 0 || ctx->height <= 0) {
617
+ ip_context_set_error(ctx, IP_ERR_INVALID_IMAGE, "invalid JPEG dimensions");
618
+ return 0;
619
+ }
620
+
621
+ if (ctx->width > ctx->max_width) {
622
+ ip_context_set_error(ctx, IP_ERR_LIMIT, "image width exceeds max_width");
623
+ return 0;
624
+ }
625
+ if (ctx->height > ctx->max_height) {
626
+ ip_context_set_error(ctx, IP_ERR_LIMIT, "image height exceeds max_height");
627
+ return 0;
628
+ }
629
+ if ((uint64_t)ctx->width * (uint64_t)ctx->height > ctx->max_pixels) {
630
+ ip_context_set_error(ctx, IP_ERR_LIMIT, "image pixels exceed max_pixels");
631
+ return 0;
632
+ }
633
+
634
+ return 1;
635
+ }
636
+
637
+ static VALUE ip_inspect_image_entry(VALUE self, VALUE input, VALUE input_kind) {
638
+ (void)self;
639
+ ip_context_t *ctx = ip_context_new();
640
+ if (!ctx)
641
+ rb_raise(rb_eImagePackOutOfMemoryError, "failed to allocate native context");
642
+
643
+ if (!ip_prepare_input_bytes(ctx, input, ip_parse_input_kind(input_kind)) ||
644
+ !ip_inspect_jpeg_header(ctx)) {
645
+ VALUE exception = ip_status_to_exception(ctx->status);
646
+ char message[512];
647
+ snprintf(message, sizeof(message), "%s", ctx->error_message);
648
+ ip_context_free(ctx);
649
+ rb_raise(exception, "%s", message[0] ? message : "failed to inspect JPEG image");
650
+ }
651
+
652
+ VALUE hash = rb_hash_new();
653
+ rb_hash_aset(hash, ID2SYM(rb_intern("format")), ID2SYM(rb_intern("jpeg")));
654
+ rb_hash_aset(hash, ID2SYM(rb_intern("width")), INT2NUM(ctx->width));
655
+ rb_hash_aset(hash, ID2SYM(rb_intern("height")), INT2NUM(ctx->height));
656
+ rb_hash_aset(hash, ID2SYM(rb_intern("channels")), INT2NUM(ctx->channels));
657
+ rb_hash_aset(hash, ID2SYM(rb_intern("bit_depth")), INT2NUM(ctx->bit_depth));
658
+ rb_hash_aset(hash, ID2SYM(rb_intern("decoded_bytes")), SIZET2NUM(ctx->decoded_bytes));
659
+ ip_context_free(ctx);
660
+ return hash;
661
+ }
662
+
663
+ static J_COLOR_SPACE color_space_for_channels(int channels) {
664
+ return channels == 1 ? JCS_GRAYSCALE : JCS_RGB;
665
+ }
666
+
667
+ static void configure_mozjpeg_profile_before_defaults(struct jpeg_compress_struct *cinfo,
668
+ int mozjpeg_size_mode) {
669
+ jpeg_c_set_int_param(cinfo, JINT_COMPRESS_PROFILE,
670
+ mozjpeg_size_mode ? JCP_MAX_COMPRESSION : JCP_FASTEST);
671
+ }
672
+
673
+ static void configure_mozjpeg_features_after_defaults(struct jpeg_compress_struct *cinfo,
674
+ int mozjpeg_size_mode,
675
+ int progressive_requested,
676
+ int mozjpeg_trellis_enabled) {
677
+ if (mozjpeg_size_mode) {
678
+ cinfo->optimize_coding = TRUE;
679
+
680
+ if (!mozjpeg_trellis_enabled) {
681
+ jpeg_c_set_bool_param(cinfo, JBOOLEAN_TRELLIS_QUANT, FALSE);
682
+ jpeg_c_set_bool_param(cinfo, JBOOLEAN_TRELLIS_QUANT_DC, FALSE);
683
+ jpeg_c_set_bool_param(cinfo, JBOOLEAN_TRELLIS_EOB_OPT, FALSE);
684
+ jpeg_c_set_bool_param(cinfo, JBOOLEAN_USE_SCANS_IN_TRELLIS, FALSE);
685
+ jpeg_c_set_bool_param(cinfo, JBOOLEAN_TRELLIS_Q_OPT, FALSE);
686
+ jpeg_c_set_bool_param(cinfo, JBOOLEAN_OVERSHOOT_DERINGING, FALSE);
687
+ }
688
+
689
+ return;
690
+ }
691
+
692
+ cinfo->optimize_coding = FALSE;
693
+ #if defined(IMAGE_PACK_HAS_SIMD)
694
+ cinfo->dct_method = JDCT_ISLOW;
695
+ #else
696
+ cinfo->dct_method = JDCT_FASTEST;
697
+ #endif
698
+ cinfo->smoothing_factor = 0;
699
+ jpeg_c_set_bool_param(cinfo, JBOOLEAN_OPTIMIZE_SCANS, FALSE);
700
+ jpeg_c_set_bool_param(cinfo, JBOOLEAN_TRELLIS_QUANT, FALSE);
701
+ jpeg_c_set_bool_param(cinfo, JBOOLEAN_TRELLIS_QUANT_DC, FALSE);
702
+ jpeg_c_set_bool_param(cinfo, JBOOLEAN_TRELLIS_EOB_OPT, FALSE);
703
+ jpeg_c_set_bool_param(cinfo, JBOOLEAN_USE_SCANS_IN_TRELLIS, FALSE);
704
+ jpeg_c_set_bool_param(cinfo, JBOOLEAN_TRELLIS_Q_OPT, FALSE);
705
+ jpeg_c_set_bool_param(cinfo, JBOOLEAN_OVERSHOOT_DERINGING, FALSE);
706
+
707
+ if (progressive_requested) {
708
+ jpeg_simple_progression(cinfo);
709
+ cinfo->optimize_coding = TRUE;
710
+ }
711
+ }
712
+
713
+ static int prepare_encode_row(ip_context_t *ctx, JDIMENSION y, JSAMPROW *row) {
714
+ if (ctx->channels == 4) {
715
+ size_t rgb_row_size = 0;
716
+ if (!ip_checked_mul_size((size_t)ctx->width, 3, &rgb_row_size)) {
717
+ ip_context_set_error(ctx, IP_ERR_LIMIT, "RGBA scratch row size overflow");
718
+ return 0;
719
+ }
720
+
721
+ if (ctx->scratch_row_size < rgb_row_size) {
722
+ unsigned char *new_row = (unsigned char *)realloc(ctx->scratch_row, rgb_row_size);
723
+ if (!new_row) {
724
+ ip_context_set_error(ctx, IP_ERR_OOM, "failed to allocate RGBA scratch row");
725
+ return 0;
726
+ }
727
+ ctx->scratch_row = new_row;
728
+ ctx->scratch_row_size = rgb_row_size;
729
+ }
730
+
731
+ const unsigned char *src = ctx->pixel_data + ((size_t)y * (size_t)ctx->width * 4);
732
+ unsigned char *dst = ctx->scratch_row;
733
+ for (int x = 0; x < ctx->width; x++) {
734
+ dst[x * 3 + 0] = src[x * 4 + 0];
735
+ dst[x * 3 + 1] = src[x * 4 + 1];
736
+ dst[x * 3 + 2] = src[x * 4 + 2];
737
+ }
738
+ *row = ctx->scratch_row;
739
+ return 1;
740
+ }
741
+
742
+ *row = (JSAMPROW)(ctx->pixel_data + ((size_t)y * (size_t)ctx->width * (size_t)ctx->channels));
743
+ return 1;
744
+ }
745
+
746
+ static int encode_pixels_with_libjpeg(ip_context_t *ctx, int mozjpeg_size_mode) {
747
+ struct jpeg_compress_struct cinfo;
748
+ ip_jpeg_error_mgr jerr;
749
+ memset(&cinfo, 0, sizeof(cinfo));
750
+ memset(&jerr, 0, sizeof(jerr));
751
+
752
+ cinfo.err = jpeg_std_error(&jerr.pub);
753
+ jerr.pub.error_exit = ip_jpeg_encode_error_exit;
754
+ jerr.ctx = ctx;
755
+
756
+ ctx->jmp_armed = 1;
757
+ if (setjmp(ctx->jmpbuf)) {
758
+ ctx->jmp_armed = 0;
759
+ jpeg_destroy_compress(&cinfo);
760
+ if (ctx->status == IP_OK)
761
+ ip_context_set_error(ctx, IP_ERR_ENCODE, "JPEG encode failed");
762
+ return 0;
763
+ }
764
+
765
+ jpeg_create_compress(&cinfo);
766
+
767
+ unsigned char *jpeg_buf = NULL;
768
+ unsigned long jpeg_size = 0;
769
+ jpeg_mem_dest(&cinfo, &jpeg_buf, &jpeg_size);
770
+
771
+ cinfo.image_width = (JDIMENSION)ctx->width;
772
+ cinfo.image_height = (JDIMENSION)ctx->height;
773
+ cinfo.input_components = ctx->channels == 4 ? 3 : ctx->channels;
774
+ cinfo.in_color_space = color_space_for_channels(cinfo.input_components);
775
+
776
+ configure_mozjpeg_profile_before_defaults(&cinfo, mozjpeg_size_mode);
777
+ jpeg_set_defaults(&cinfo);
778
+ jpeg_set_quality(&cinfo, ctx->quality, TRUE);
779
+ configure_mozjpeg_features_after_defaults(&cinfo, mozjpeg_size_mode, ctx->progressive,
780
+ ctx->mozjpeg_trellis_enabled);
781
+
782
+ jpeg_start_compress(&cinfo, TRUE);
783
+
784
+ while (cinfo.next_scanline < cinfo.image_height) {
785
+ if (ctx->cancellable_requested && (cinfo.next_scanline % 16 == 0) &&
786
+ atomic_load(&ctx->cancelled)) {
787
+ ip_context_set_error(ctx, IP_ERR_CANCELLED, "JPEG encode cancelled");
788
+ jpeg_abort_compress(&cinfo);
789
+ jpeg_destroy_compress(&cinfo);
790
+ free(jpeg_buf);
791
+ ctx->jmp_armed = 0;
792
+ return 0;
793
+ }
794
+
795
+ JSAMPROW row = NULL;
796
+ if (!prepare_encode_row(ctx, cinfo.next_scanline, &row)) {
797
+ jpeg_abort_compress(&cinfo);
798
+ jpeg_destroy_compress(&cinfo);
799
+ free(jpeg_buf);
800
+ ctx->jmp_armed = 0;
801
+ return 0;
802
+ }
803
+
804
+ jpeg_write_scanlines(&cinfo, &row, 1);
805
+ }
806
+
807
+ jpeg_finish_compress(&cinfo);
808
+ jpeg_destroy_compress(&cinfo);
809
+ ctx->jmp_armed = 0;
810
+
811
+ if ((size_t)jpeg_size > ctx->max_output_size) {
812
+ free(jpeg_buf);
813
+ ip_context_set_error(ctx, IP_ERR_LIMIT, "output exceeds max_output_size");
814
+ return 0;
815
+ }
816
+
817
+ ctx->output_data = jpeg_buf;
818
+ ctx->output_size = (size_t)jpeg_size;
819
+ ctx->output_capacity = (size_t)jpeg_size;
820
+ ctx->output_owner = IP_OUTPUT_OWNER_MALLOC;
821
+ return 1;
822
+ }
823
+
824
+ static int ip_jpeg_decode_to_pixels(ip_context_t *ctx, unsigned char **pixels, int *width,
825
+ int *height, int *channels, int fast_decode_mode) {
826
+ struct jpeg_decompress_struct cinfo;
827
+ ip_jpeg_error_mgr jerr;
828
+ memset(&cinfo, 0, sizeof(cinfo));
829
+ memset(&jerr, 0, sizeof(jerr));
830
+
831
+ cinfo.err = jpeg_std_error(&jerr.pub);
832
+ jerr.pub.error_exit = ip_jpeg_invalid_error_exit;
833
+ jerr.ctx = ctx;
834
+
835
+ ctx->jmp_armed = 1;
836
+ if (setjmp(ctx->jmpbuf)) {
837
+ ctx->jmp_armed = 0;
838
+ jpeg_destroy_decompress(&cinfo);
839
+ if (ctx->status == IP_OK)
840
+ ip_context_set_error(ctx, IP_ERR_INVALID_IMAGE, "JPEG decode failed");
841
+ return 0;
842
+ }
843
+
844
+ jpeg_create_decompress(&cinfo);
845
+ jpeg_mem_src(&cinfo, ctx->input_data, (unsigned long)ctx->input_size);
846
+ int rc = jpeg_read_header(&cinfo, TRUE);
847
+ if (rc != JPEG_HEADER_OK) {
848
+ jpeg_destroy_decompress(&cinfo);
849
+ ctx->jmp_armed = 0;
850
+ ip_context_set_error(ctx, IP_ERR_INVALID_IMAGE, "invalid JPEG header");
851
+ return 0;
852
+ }
853
+
854
+ if (cinfo.image_width > (JDIMENSION)INT_MAX || cinfo.image_height > (JDIMENSION)INT_MAX) {
855
+ jpeg_destroy_decompress(&cinfo);
856
+ ctx->jmp_armed = 0;
857
+ ip_context_set_error(ctx, IP_ERR_LIMIT, "JPEG dimensions exceed native int range");
858
+ return 0;
859
+ }
860
+
861
+ int ch = cinfo.num_components == 1 ? 1 : 3;
862
+
863
+ ctx->width = (int)cinfo.image_width;
864
+ ctx->height = (int)cinfo.image_height;
865
+ ctx->channels = ch;
866
+ if (!ip_checked_image_size(ctx->width, ctx->height, ctx->channels, &ctx->decoded_bytes)) {
867
+ jpeg_destroy_decompress(&cinfo);
868
+ ctx->jmp_armed = 0;
869
+ ip_context_set_error(ctx, IP_ERR_LIMIT, "decoded image size overflows native size");
870
+ return 0;
871
+ }
872
+ validate_limits_for_pixels(ctx);
873
+ if (ctx->status != IP_OK) {
874
+ jpeg_destroy_decompress(&cinfo);
875
+ ctx->jmp_armed = 0;
876
+ return 0;
877
+ }
878
+
879
+ cinfo.out_color_space = ch == 1 ? JCS_GRAYSCALE : JCS_RGB;
880
+
881
+ if (fast_decode_mode) {
882
+ #if defined(IMAGE_PACK_HAS_SIMD)
883
+ cinfo.dct_method = JDCT_ISLOW;
884
+ #else
885
+ cinfo.dct_method = JDCT_FASTEST;
886
+ #endif
887
+ cinfo.do_fancy_upsampling = FALSE;
888
+ cinfo.do_block_smoothing = FALSE;
889
+ cinfo.quantize_colors = FALSE;
890
+ cinfo.two_pass_quantize = FALSE;
891
+ cinfo.dither_mode = JDITHER_NONE;
892
+ }
893
+
894
+ jpeg_start_decompress(&cinfo);
895
+ size_t row_stride = 0;
896
+ size_t size = 0;
897
+ if (!ip_checked_mul_size((size_t)cinfo.output_width, (size_t)cinfo.output_components,
898
+ &row_stride) ||
899
+ !ip_checked_mul_size(row_stride, (size_t)cinfo.output_height, &size)) {
900
+ jpeg_destroy_decompress(&cinfo);
901
+ ctx->jmp_armed = 0;
902
+ ip_context_set_error(ctx, IP_ERR_LIMIT, "decoded image buffer size overflow");
903
+ return 0;
904
+ }
905
+ unsigned char *buf = (unsigned char *)malloc(size);
906
+ if (!buf && size > 0) {
907
+ jpeg_destroy_decompress(&cinfo);
908
+ ctx->jmp_armed = 0;
909
+ ip_context_set_error(ctx, IP_ERR_OOM, "failed to allocate decoded pixel buffer");
910
+ return 0;
911
+ }
912
+
913
+ while (cinfo.output_scanline < cinfo.output_height) {
914
+ JSAMPROW row = buf + ((size_t)cinfo.output_scanline * row_stride);
915
+ jpeg_read_scanlines(&cinfo, &row, 1);
916
+ }
917
+
918
+ int out_width = (int)cinfo.output_width;
919
+ int out_height = (int)cinfo.output_height;
920
+
921
+ jpeg_finish_decompress(&cinfo);
922
+ jpeg_destroy_decompress(&cinfo);
923
+ ctx->jmp_armed = 0;
924
+
925
+ *pixels = buf;
926
+ *width = out_width;
927
+ *height = out_height;
928
+ *channels = ch;
929
+ return 1;
930
+ }
931
+
932
+ static int compress_jpeg_input_with_mode(ip_context_t *ctx, int mozjpeg_size_mode) {
933
+ if (ctx->pixel_data) {
934
+ return encode_pixels_with_libjpeg(ctx, mozjpeg_size_mode);
935
+ }
936
+
937
+ unsigned char *pixels = NULL;
938
+ int width = 0;
939
+ int height = 0;
940
+ int channels = 0;
941
+ if (!ip_jpeg_decode_to_pixels(ctx, &pixels, &width, &height, &channels, !mozjpeg_size_mode))
942
+ return 0;
943
+
944
+ ctx->owned_pixel_data = pixels;
945
+ ctx->pixel_data = pixels;
946
+ ctx->pixel_size = (size_t)width * (size_t)height * (size_t)channels;
947
+ ctx->width = width;
948
+ ctx->height = height;
949
+ ctx->channels = channels;
950
+ ctx->decoded_bytes = ctx->pixel_size;
951
+
952
+ return encode_pixels_with_libjpeg(ctx, mozjpeg_size_mode);
953
+ }
954
+
955
+ static void ip_clear_output_buffer(ip_context_t *ctx) {
956
+ if (!ctx)
957
+ return;
958
+
959
+ if (ctx->output_data && ctx->output_owner == IP_OUTPUT_OWNER_MALLOC)
960
+ free(ctx->output_data);
961
+
962
+ ctx->output_data = NULL;
963
+ ctx->output_size = 0;
964
+ ctx->output_capacity = 0;
965
+ ctx->output_owner = IP_OUTPUT_OWNER_NONE;
966
+ }
967
+
968
+ static int ip_decode_jpeg_buffer_preserving_context(ip_context_t *ctx, const unsigned char *data,
969
+ size_t size, unsigned char **pixels, int *width,
970
+ int *height, int *channels) {
971
+ const unsigned char *old_input_data = ctx->input_data;
972
+ size_t old_input_size = ctx->input_size;
973
+ int old_width = ctx->width;
974
+ int old_height = ctx->height;
975
+ int old_channels = ctx->channels;
976
+ int old_bit_depth = ctx->bit_depth;
977
+ size_t old_decoded_bytes = ctx->decoded_bytes;
978
+
979
+ ctx->input_data = data;
980
+ ctx->input_size = size;
981
+ int ok = ip_jpeg_decode_to_pixels(ctx, pixels, width, height, channels, 0);
982
+
983
+ ctx->input_data = old_input_data;
984
+ ctx->input_size = old_input_size;
985
+ ctx->width = old_width;
986
+ ctx->height = old_height;
987
+ ctx->channels = old_channels;
988
+ ctx->bit_depth = old_bit_depth;
989
+ ctx->decoded_bytes = old_decoded_bytes;
990
+ return ok;
991
+ }
992
+
993
+ static unsigned char *ip_build_luma_buffer(ip_context_t *ctx, const unsigned char *pixels,
994
+ int width, int height, int channels) {
995
+ size_t count = 0;
996
+ if (!ip_checked_mul_size((size_t)width, (size_t)height, &count)) {
997
+ ip_context_set_error(ctx, IP_ERR_LIMIT, "luma buffer size overflow");
998
+ return NULL;
999
+ }
1000
+
1001
+ unsigned char *luma = (unsigned char *)malloc(count);
1002
+ if (!luma) {
1003
+ ip_context_set_error(ctx, IP_ERR_OOM, "failed to allocate luma buffer");
1004
+ return NULL;
1005
+ }
1006
+
1007
+ if (channels == 1) {
1008
+ memcpy(luma, pixels, count);
1009
+ return luma;
1010
+ }
1011
+
1012
+ for (size_t i = 0; i < count; i++) {
1013
+ const unsigned char *p = pixels + (i * (size_t)channels);
1014
+ unsigned int y =
1015
+ 77u * (unsigned int)p[0] + 150u * (unsigned int)p[1] + 29u * (unsigned int)p[2] + 128u;
1016
+ luma[i] = (unsigned char)(y >> 8);
1017
+ }
1018
+
1019
+ return luma;
1020
+ }
1021
+
1022
+ static double ip_compute_ssim_luma_buffer(const unsigned char *a, const unsigned char *b, int width,
1023
+ int height) {
1024
+ const int window = 8;
1025
+ const double c1 = 6.5025; /* (0.01 * 255)^2 */
1026
+ const double c2 = 58.5225; /* (0.03 * 255)^2 */
1027
+ double total_ssim = 0.0;
1028
+ int windows = 0;
1029
+
1030
+ for (int y0 = 0; y0 < height; y0 += window) {
1031
+ int y1 = y0 + window;
1032
+ if (y1 > height)
1033
+ y1 = height;
1034
+
1035
+ for (int x0 = 0; x0 < width; x0 += window) {
1036
+ int x1 = x0 + window;
1037
+ if (x1 > width)
1038
+ x1 = width;
1039
+
1040
+ double sum_a = 0.0;
1041
+ double sum_b = 0.0;
1042
+ double sum_a2 = 0.0;
1043
+ double sum_b2 = 0.0;
1044
+ double sum_ab = 0.0;
1045
+ int n = 0;
1046
+
1047
+ for (int y = y0; y < y1; y++) {
1048
+ size_t row = (size_t)y * (size_t)width;
1049
+ for (int x = x0; x < x1; x++) {
1050
+ size_t idx = row + (size_t)x;
1051
+ double la = (double)a[idx];
1052
+ double lb = (double)b[idx];
1053
+ sum_a += la;
1054
+ sum_b += lb;
1055
+ sum_a2 += la * la;
1056
+ sum_b2 += lb * lb;
1057
+ sum_ab += la * lb;
1058
+ n++;
1059
+ }
1060
+ }
1061
+
1062
+ if (n <= 0)
1063
+ continue;
1064
+
1065
+ double inv_n = 1.0 / (double)n;
1066
+ double mean_a = sum_a * inv_n;
1067
+ double mean_b = sum_b * inv_n;
1068
+ double var_a = (sum_a2 * inv_n) - (mean_a * mean_a);
1069
+ double var_b = (sum_b2 * inv_n) - (mean_b * mean_b);
1070
+ double cov_ab = (sum_ab * inv_n) - (mean_a * mean_b);
1071
+
1072
+ if (var_a < 0.0)
1073
+ var_a = 0.0;
1074
+ if (var_b < 0.0)
1075
+ var_b = 0.0;
1076
+
1077
+ double numerator = (2.0 * mean_a * mean_b + c1) * (2.0 * cov_ab + c2);
1078
+ double denominator = (mean_a * mean_a + mean_b * mean_b + c1) * (var_a + var_b + c2);
1079
+ double ssim = denominator == 0.0 ? 1.0 : numerator / denominator;
1080
+
1081
+ if (ssim < 0.0)
1082
+ ssim = 0.0;
1083
+ if (ssim > 1.0)
1084
+ ssim = 1.0;
1085
+
1086
+ total_ssim += ssim;
1087
+ windows++;
1088
+ }
1089
+ }
1090
+
1091
+ return windows > 0 ? total_ssim / (double)windows : 0.0;
1092
+ }
1093
+
1094
+ static int guarded_compress_jpeg_input_with_mode(ip_context_t *ctx, int mozjpeg_size_mode) {
1095
+ unsigned char *reference_pixels = NULL;
1096
+ int reference_width = 0;
1097
+ int reference_height = 0;
1098
+ int reference_channels = 0;
1099
+
1100
+ if (ctx->pixel_data) {
1101
+ reference_pixels = ctx->owned_pixel_data;
1102
+ reference_width = ctx->width;
1103
+ reference_height = ctx->height;
1104
+ reference_channels = ctx->channels;
1105
+ } else {
1106
+ if (!ip_jpeg_decode_to_pixels(ctx, &reference_pixels, &reference_width, &reference_height,
1107
+ &reference_channels, 0)) {
1108
+ return 0;
1109
+ }
1110
+
1111
+ ctx->owned_pixel_data = reference_pixels;
1112
+ ctx->pixel_data = reference_pixels;
1113
+ ctx->pixel_size =
1114
+ (size_t)reference_width * (size_t)reference_height * (size_t)reference_channels;
1115
+ ctx->width = reference_width;
1116
+ ctx->height = reference_height;
1117
+ ctx->channels = reference_channels;
1118
+ ctx->decoded_bytes = ctx->pixel_size;
1119
+ }
1120
+
1121
+ if (reference_channels == 4) {
1122
+ ip_context_set_error(ctx, IP_ERR_UNSUPPORTED,
1123
+ "min_ssim is not supported for RGBA input in v0.2.0");
1124
+ return 0;
1125
+ }
1126
+
1127
+ unsigned char *reference_luma = ip_build_luma_buffer(ctx, reference_pixels, reference_width,
1128
+ reference_height, reference_channels);
1129
+ if (!reference_luma)
1130
+ return 0;
1131
+
1132
+ int search_low = ctx->quality;
1133
+ int search_high = 100;
1134
+ int best_quality = -1;
1135
+ double best_ssim = 0.0;
1136
+ unsigned char *best_jpeg = NULL;
1137
+ size_t best_jpeg_size = 0;
1138
+ double best_seen_ssim = 0.0;
1139
+ int best_seen_quality = 0;
1140
+
1141
+ while (search_low <= search_high) {
1142
+ if (ctx->cancellable_requested && atomic_load(&ctx->cancelled)) {
1143
+ free(reference_luma);
1144
+ free(best_jpeg);
1145
+ ip_context_set_error(ctx, IP_ERR_CANCELLED, "SSIM-guarded JPEG encode cancelled");
1146
+ return 0;
1147
+ }
1148
+
1149
+ int trial_quality = search_low + ((search_high - search_low) / 2);
1150
+ ctx->quality = trial_quality;
1151
+ ip_clear_output_buffer(ctx);
1152
+
1153
+ if (!encode_pixels_with_libjpeg(ctx, mozjpeg_size_mode)) {
1154
+ free(reference_luma);
1155
+ free(best_jpeg);
1156
+ return 0;
1157
+ }
1158
+
1159
+ unsigned char *candidate_jpeg = ctx->output_data;
1160
+ size_t candidate_jpeg_size = ctx->output_size;
1161
+ ctx->output_data = NULL;
1162
+ ctx->output_size = 0;
1163
+ ctx->output_capacity = 0;
1164
+ ctx->output_owner = IP_OUTPUT_OWNER_NONE;
1165
+
1166
+ unsigned char *candidate_pixels = NULL;
1167
+ int candidate_width = 0;
1168
+ int candidate_height = 0;
1169
+ int candidate_channels = 0;
1170
+ int decoded_ok = ip_decode_jpeg_buffer_preserving_context(
1171
+ ctx, candidate_jpeg, candidate_jpeg_size, &candidate_pixels, &candidate_width,
1172
+ &candidate_height, &candidate_channels);
1173
+
1174
+ if (!decoded_ok) {
1175
+ free(reference_luma);
1176
+ free(candidate_jpeg);
1177
+ free(best_jpeg);
1178
+ return 0;
1179
+ }
1180
+
1181
+ if (candidate_width != reference_width || candidate_height != reference_height ||
1182
+ candidate_channels != reference_channels) {
1183
+ free(reference_luma);
1184
+ free(candidate_pixels);
1185
+ free(candidate_jpeg);
1186
+ free(best_jpeg);
1187
+ ip_context_set_error(ctx, IP_ERR_ENCODE,
1188
+ "candidate JPEG dimensions differ from reference image");
1189
+ return 0;
1190
+ }
1191
+
1192
+ unsigned char *candidate_luma = ip_build_luma_buffer(ctx, candidate_pixels, candidate_width,
1193
+ candidate_height, candidate_channels);
1194
+ free(candidate_pixels);
1195
+
1196
+ if (!candidate_luma) {
1197
+ free(reference_luma);
1198
+ free(candidate_jpeg);
1199
+ free(best_jpeg);
1200
+ return 0;
1201
+ }
1202
+
1203
+ double ssim = ip_compute_ssim_luma_buffer(reference_luma, candidate_luma, reference_width,
1204
+ reference_height);
1205
+ free(candidate_luma);
1206
+
1207
+ if (ssim > best_seen_ssim) {
1208
+ best_seen_ssim = ssim;
1209
+ best_seen_quality = trial_quality;
1210
+ }
1211
+
1212
+ if (ssim >= ctx->min_ssim) {
1213
+ free(best_jpeg);
1214
+ best_jpeg = candidate_jpeg;
1215
+ best_jpeg_size = candidate_jpeg_size;
1216
+ best_quality = trial_quality;
1217
+ best_ssim = ssim;
1218
+ search_high = trial_quality - 1;
1219
+ } else {
1220
+ free(candidate_jpeg);
1221
+ search_low = trial_quality + 1;
1222
+ }
1223
+ }
1224
+
1225
+ if (best_quality < 0) {
1226
+ char message[512];
1227
+ snprintf(message, sizeof(message),
1228
+ "cannot satisfy min_ssim=%.6f; best observed SSIM %.6f at quality=%d",
1229
+ ctx->min_ssim, best_seen_ssim, best_seen_quality);
1230
+ free(reference_luma);
1231
+ ip_context_set_error(ctx, IP_ERR_QUALITY, message);
1232
+ return 0;
1233
+ }
1234
+
1235
+ ctx->quality = best_quality;
1236
+ ctx->selected_quality = best_quality;
1237
+ ctx->measured_ssim = best_ssim;
1238
+ free(reference_luma);
1239
+
1240
+ ctx->output_data = best_jpeg;
1241
+ ctx->output_size = best_jpeg_size;
1242
+ ctx->output_capacity = best_jpeg_size;
1243
+ ctx->output_owner = IP_OUTPUT_OWNER_MALLOC;
1244
+ return 1;
1245
+ }
1246
+
1247
+ #if defined(IMAGE_PACK_ENABLE_LOSSLESS_TRANSCODE_FAST_PATH)
1248
+ static int ip_jpeg_transcode_coefficients(ip_context_t *ctx, int mozjpeg_size_mode) {
1249
+ if (mozjpeg_size_mode)
1250
+ return 0;
1251
+ if (!ctx->input_data || ctx->input_size < 4)
1252
+ return 0;
1253
+ if (ctx->input_data[0] != 0xFF || ctx->input_data[1] != 0xD8)
1254
+ return 0;
1255
+
1256
+ struct jpeg_decompress_struct srcinfo;
1257
+ struct jpeg_compress_struct dstinfo;
1258
+ ip_jpeg_error_mgr jsrcerr;
1259
+ ip_jpeg_error_mgr jdsterr;
1260
+ memset(&srcinfo, 0, sizeof(srcinfo));
1261
+ memset(&dstinfo, 0, sizeof(dstinfo));
1262
+ memset(&jsrcerr, 0, sizeof(jsrcerr));
1263
+ memset(&jdsterr, 0, sizeof(jdsterr));
1264
+
1265
+ srcinfo.err = jpeg_std_error(&jsrcerr.pub);
1266
+ jsrcerr.pub.error_exit = ip_jpeg_invalid_error_exit;
1267
+ jsrcerr.ctx = ctx;
1268
+
1269
+ dstinfo.err = jpeg_std_error(&jdsterr.pub);
1270
+ jdsterr.pub.error_exit = ip_jpeg_encode_error_exit;
1271
+ jdsterr.ctx = ctx;
1272
+
1273
+ unsigned char *jpeg_buf = NULL;
1274
+ unsigned long jpeg_size = 0;
1275
+ int src_created = 0;
1276
+ int dst_created = 0;
1277
+
1278
+ ctx->jmp_armed = 1;
1279
+ if (setjmp(ctx->jmpbuf)) {
1280
+ ctx->jmp_armed = 0;
1281
+ if (dst_created)
1282
+ jpeg_destroy_compress(&dstinfo);
1283
+ if (src_created)
1284
+ jpeg_destroy_decompress(&srcinfo);
1285
+ free(jpeg_buf);
1286
+
1287
+ if (ctx->status == IP_OK)
1288
+ return 0;
1289
+ return 0;
1290
+ }
1291
+
1292
+ jpeg_create_decompress(&srcinfo);
1293
+ src_created = 1;
1294
+ jpeg_mem_src(&srcinfo, ctx->input_data, (unsigned long)ctx->input_size);
1295
+
1296
+ if (jpeg_read_header(&srcinfo, TRUE) != JPEG_HEADER_OK) {
1297
+ jpeg_destroy_decompress(&srcinfo);
1298
+ ctx->jmp_armed = 0;
1299
+ return 0;
1300
+ }
1301
+
1302
+ if ((int)srcinfo.image_width > ctx->max_width || (int)srcinfo.image_height > ctx->max_height ||
1303
+ (uint64_t)srcinfo.image_width * (uint64_t)srcinfo.image_height > ctx->max_pixels) {
1304
+ jpeg_destroy_decompress(&srcinfo);
1305
+ ctx->jmp_armed = 0;
1306
+ ip_context_set_error(ctx, IP_ERR_LIMIT, "image exceeds configured limits");
1307
+ return 0;
1308
+ }
1309
+
1310
+ jvirt_barray_ptr *src_coef_arrays = jpeg_read_coefficients(&srcinfo);
1311
+ if (!src_coef_arrays) {
1312
+ jpeg_destroy_decompress(&srcinfo);
1313
+ ctx->jmp_armed = 0;
1314
+ return 0;
1315
+ }
1316
+
1317
+ jpeg_create_compress(&dstinfo);
1318
+ dst_created = 1;
1319
+ jpeg_mem_dest(&dstinfo, &jpeg_buf, &jpeg_size);
1320
+ jpeg_copy_critical_parameters(&srcinfo, &dstinfo);
1321
+
1322
+ dstinfo.optimize_coding = FALSE;
1323
+ if (ctx->progressive) {
1324
+ jpeg_simple_progression(&dstinfo);
1325
+ dstinfo.optimize_coding = TRUE;
1326
+ }
1327
+
1328
+ jpeg_write_coefficients(&dstinfo, src_coef_arrays);
1329
+ jpeg_finish_compress(&dstinfo);
1330
+ jpeg_destroy_compress(&dstinfo);
1331
+ dst_created = 0;
1332
+
1333
+ jpeg_finish_decompress(&srcinfo);
1334
+ jpeg_destroy_decompress(&srcinfo);
1335
+ src_created = 0;
1336
+
1337
+ ctx->jmp_armed = 0;
1338
+
1339
+ if ((size_t)jpeg_size > ctx->max_output_size) {
1340
+ free(jpeg_buf);
1341
+ ip_context_set_error(ctx, IP_ERR_LIMIT, "transcoded output exceeds max_output_size");
1342
+ return 0;
1343
+ }
1344
+
1345
+ ctx->output_data = jpeg_buf;
1346
+ ctx->output_size = (size_t)jpeg_size;
1347
+ ctx->output_capacity = (size_t)jpeg_size;
1348
+ ctx->output_owner = IP_OUTPUT_OWNER_MALLOC;
1349
+
1350
+ if (ctx->width == 0 || ctx->height == 0) {
1351
+ ctx->width = 0;
1352
+ ctx->height = 0;
1353
+ ctx->channels = 0;
1354
+ }
1355
+ return 1;
1356
+ }
1357
+
1358
+ #endif
1359
+
1360
+ static int ip_jpeg_turbo_compress(ip_context_t *ctx) {
1361
+ if (ctx->ssim_guard_enabled)
1362
+ return guarded_compress_jpeg_input_with_mode(ctx, 0);
1363
+ return compress_jpeg_input_with_mode(ctx, 0);
1364
+ }
1365
+
1366
+ static int ip_mozjpeg_compress(ip_context_t *ctx) {
1367
+ if (ctx->ssim_guard_enabled)
1368
+ return guarded_compress_jpeg_input_with_mode(ctx, 1);
1369
+ return compress_jpeg_input_with_mode(ctx, 1);
1370
+ }
1371
+
1372
+ static void ip_resolve_execution(ip_context_t *ctx) {
1373
+ if (ctx->requested_execution != IP_EXEC_AUTO) {
1374
+ ctx->resolved_execution = ctx->requested_execution;
1375
+ return;
1376
+ }
1377
+
1378
+ if (ctx->cancellable_requested) {
1379
+ ctx->resolved_execution = ctx->has_scheduler ? IP_EXEC_OFFLOAD : IP_EXEC_NOGVL;
1380
+ return;
1381
+ }
1382
+
1383
+ if (ctx->ssim_guard_enabled) {
1384
+ ctx->resolved_execution = ctx->has_scheduler ? IP_EXEC_OFFLOAD : IP_EXEC_NOGVL;
1385
+ return;
1386
+ }
1387
+
1388
+ if (ctx->input_size < ctx->direct_input_threshold &&
1389
+ ctx->decoded_bytes < ctx->direct_pixel_threshold) {
1390
+ ctx->resolved_execution = IP_EXEC_DIRECT;
1391
+ return;
1392
+ }
1393
+
1394
+ ctx->resolved_execution = ctx->has_scheduler ? IP_EXEC_OFFLOAD : IP_EXEC_NOGVL;
1395
+ }
1396
+
1397
+ static void ip_unblock_function(void *data) {
1398
+ ip_context_t *ctx = (ip_context_t *)data;
1399
+ atomic_store(&ctx->cancelled, 1);
1400
+ }
1401
+
1402
+ static void *ip_run_encode_nogvl(void *data) {
1403
+ ip_context_t *ctx = (ip_context_t *)data;
1404
+
1405
+ if (ctx->status != IP_OK)
1406
+ return NULL;
1407
+
1408
+ switch (ctx->algo) {
1409
+ case IP_ALGO_JPEG_TURBO:
1410
+ ip_jpeg_turbo_compress(ctx);
1411
+ break;
1412
+ case IP_ALGO_MOZJPEG:
1413
+ ip_mozjpeg_compress(ctx);
1414
+ break;
1415
+ default:
1416
+ ip_context_set_error(ctx, IP_ERR_UNSUPPORTED, "unsupported algorithm");
1417
+ break;
1418
+ }
1419
+
1420
+ return NULL;
1421
+ }
1422
+
1423
+ static int ip_run_context(ip_context_t *ctx) {
1424
+ ip_resolve_execution(ctx);
1425
+
1426
+ if (ctx->resolved_execution == IP_EXEC_DIRECT) {
1427
+ ip_run_encode_nogvl(ctx);
1428
+ } else if (ctx->resolved_execution == IP_EXEC_NOGVL) {
1429
+ rb_nogvl(ip_run_encode_nogvl, ctx, ip_unblock_function, ctx, 0);
1430
+ } else if (ctx->resolved_execution == IP_EXEC_OFFLOAD) {
1431
+ rb_nogvl(ip_run_encode_nogvl, ctx, ip_unblock_function, ctx, RB_NOGVL_OFFLOAD_SAFE);
1432
+ } else {
1433
+ ip_context_set_error(ctx, IP_ERR_INVALID_ARGUMENT, "invalid resolved execution mode");
1434
+ }
1435
+
1436
+ return ctx->status == IP_OK;
1437
+ }
1438
+
1439
+ static size_t config_size_value(VALUE config, ID id, size_t fallback) {
1440
+ VALUE value = rb_funcall(config, id, 0);
1441
+ if (NIL_P(value))
1442
+ return fallback;
1443
+ return NUM2SIZET(value);
1444
+ }
1445
+
1446
+ static int config_int_value(VALUE config, ID id, int fallback) {
1447
+ VALUE value = rb_funcall(config, id, 0);
1448
+ if (NIL_P(value))
1449
+ return fallback;
1450
+ return NUM2INT(value);
1451
+ }
1452
+
1453
+ static void apply_configuration(VALUE self, ip_context_t *ctx) {
1454
+ VALUE config = rb_funcall(self, id_configuration, 0);
1455
+ ctx->direct_input_threshold =
1456
+ config_size_value(config, id_direct_input_threshold, ctx->direct_input_threshold);
1457
+ ctx->direct_pixel_threshold =
1458
+ config_size_value(config, id_direct_pixel_threshold, ctx->direct_pixel_threshold);
1459
+ ctx->max_pixels = (uint64_t)config_size_value(config, id_max_pixels, (size_t)ctx->max_pixels);
1460
+ ctx->max_width = config_int_value(config, id_max_width, ctx->max_width);
1461
+ ctx->max_height = config_int_value(config, id_max_height, ctx->max_height);
1462
+ ctx->max_output_size = config_size_value(config, id_max_output_size, ctx->max_output_size);
1463
+ }
1464
+
1465
+ static void validate_limits_for_pixels(ip_context_t *ctx) {
1466
+ size_t decoded_bytes = 0;
1467
+
1468
+ if (ctx->width <= 0 || ctx->height <= 0 || ctx->channels <= 0) {
1469
+ ip_context_set_error(ctx, IP_ERR_INVALID_ARGUMENT, "image dimensions must be positive");
1470
+ return;
1471
+ }
1472
+ if (ctx->width > ctx->max_width) {
1473
+ ip_context_set_error(ctx, IP_ERR_LIMIT, "image width exceeds max_width");
1474
+ return;
1475
+ }
1476
+ if (ctx->height > ctx->max_height) {
1477
+ ip_context_set_error(ctx, IP_ERR_LIMIT, "image height exceeds max_height");
1478
+ return;
1479
+ }
1480
+ if ((uint64_t)ctx->width * (uint64_t)ctx->height > ctx->max_pixels) {
1481
+ ip_context_set_error(ctx, IP_ERR_LIMIT, "image pixels exceed max_pixels");
1482
+ return;
1483
+ }
1484
+ if (!ip_checked_image_size(ctx->width, ctx->height, ctx->channels, &decoded_bytes)) {
1485
+ ip_context_set_error(ctx, IP_ERR_LIMIT, "decoded image size overflows native size");
1486
+ return;
1487
+ }
1488
+
1489
+ ctx->decoded_bytes = decoded_bytes;
1490
+ }
1491
+
1492
+ static VALUE ip_compress_jpeg_entry(VALUE self, VALUE input, VALUE input_kind, VALUE output,
1493
+ VALUE output_kind, VALUE algo, VALUE quality, VALUE min_ssim,
1494
+ VALUE mozjpeg_trellis, VALUE progressive, VALUE strip_metadata,
1495
+ VALUE execution, VALUE cancellable, VALUE has_scheduler) {
1496
+ ip_context_t *ctx = ip_context_new();
1497
+ if (!ctx)
1498
+ rb_raise(rb_eImagePackOutOfMemoryError, "failed to allocate native context");
1499
+
1500
+ ip_output_kind_t out_kind = ip_parse_output_kind(output_kind);
1501
+ ctx->algo = ip_parse_algo(algo);
1502
+ ctx->quality = NUM2INT(quality);
1503
+ ctx->selected_quality = ctx->quality;
1504
+ ip_validate_quality_or_raise(ctx);
1505
+ ctx->min_ssim = NUM2DBL(min_ssim);
1506
+ ctx->ssim_guard_enabled = ctx->min_ssim > 0.0;
1507
+ ip_validate_min_ssim_or_raise(ctx);
1508
+ ctx->mozjpeg_trellis_enabled = ip_bool_value(mozjpeg_trellis);
1509
+ ctx->progressive = ip_bool_value(progressive);
1510
+ ctx->strip_metadata = ip_bool_value(strip_metadata);
1511
+ ctx->requested_execution = ip_parse_execution(execution);
1512
+ ctx->cancellable_requested = ip_bool_value(cancellable);
1513
+ ctx->has_scheduler = ip_bool_value(has_scheduler);
1514
+ apply_configuration(self, ctx);
1515
+
1516
+ if (!ip_prepare_input_bytes(ctx, input, ip_parse_input_kind(input_kind)) ||
1517
+ !ip_prepare_output_path(ctx, output, out_kind)) {
1518
+ VALUE exception = ip_status_to_exception(ctx->status);
1519
+ char message[512];
1520
+ snprintf(message, sizeof(message), "%s", ctx->error_message);
1521
+ ip_context_free(ctx);
1522
+ rb_raise(exception, "%s", message[0] ? message : "invalid JPEG input");
1523
+ }
1524
+
1525
+ if (ctx->requested_execution == IP_EXEC_AUTO && ctx->input_size < ctx->direct_input_threshold &&
1526
+ !ip_inspect_jpeg_header(ctx)) {
1527
+ VALUE exception = ip_status_to_exception(ctx->status);
1528
+ char message[512];
1529
+ snprintf(message, sizeof(message), "%s", ctx->error_message);
1530
+ ip_context_free(ctx);
1531
+ rb_raise(exception, "%s", message[0] ? message : "invalid JPEG input");
1532
+ }
1533
+
1534
+ ip_run_context(ctx);
1535
+ VALUE result = ip_finish_output(ctx, out_kind);
1536
+ ip_context_free(ctx);
1537
+ return result;
1538
+ }
1539
+
1540
+ static VALUE ip_compress_pixels_entry(VALUE self, VALUE buffer, VALUE width, VALUE height,
1541
+ VALUE channels, VALUE output, VALUE output_kind, VALUE algo,
1542
+ VALUE quality, VALUE progressive, VALUE execution,
1543
+ VALUE cancellable, VALUE has_scheduler) {
1544
+ ip_context_t *ctx = ip_context_new();
1545
+ if (!ctx)
1546
+ rb_raise(rb_eImagePackOutOfMemoryError, "failed to allocate native context");
1547
+
1548
+ ip_output_kind_t out_kind = ip_parse_output_kind(output_kind);
1549
+ ctx->algo = ip_parse_algo(algo);
1550
+ ctx->quality = NUM2INT(quality);
1551
+ ip_validate_quality_or_raise(ctx);
1552
+ ctx->progressive = ip_bool_value(progressive);
1553
+ ctx->strip_metadata = 1;
1554
+ ctx->requested_execution = ip_parse_execution(execution);
1555
+ ctx->cancellable_requested = ip_bool_value(cancellable);
1556
+ ctx->has_scheduler = ip_bool_value(has_scheduler);
1557
+ apply_configuration(self, ctx);
1558
+
1559
+ if (!ip_prepare_pixels(ctx, buffer, NUM2INT(width), NUM2INT(height), NUM2INT(channels)) ||
1560
+ !ip_prepare_output_path(ctx, output, out_kind)) {
1561
+ VALUE exception = ip_status_to_exception(ctx->status);
1562
+ char message[512];
1563
+ snprintf(message, sizeof(message), "%s", ctx->error_message);
1564
+ ip_context_free(ctx);
1565
+ rb_raise(exception, "%s", message[0] ? message : "invalid pixel input");
1566
+ }
1567
+
1568
+ validate_limits_for_pixels(ctx);
1569
+ if (ctx->status != IP_OK) {
1570
+ VALUE exception = ip_status_to_exception(ctx->status);
1571
+ char message[512];
1572
+ snprintf(message, sizeof(message), "%s", ctx->error_message);
1573
+ ip_context_free(ctx);
1574
+ rb_raise(exception, "%s", message);
1575
+ }
1576
+
1577
+ ip_run_context(ctx);
1578
+ VALUE result = ip_finish_output(ctx, out_kind);
1579
+ ip_context_free(ctx);
1580
+ return result;
1581
+ }
1582
+
1583
+ IMAGE_PACK_INIT_EXPORT void Init_image_pack(void) {
1584
+ id_jpeg_turbo = rb_intern("jpeg_turbo");
1585
+ id_mozjpeg = rb_intern("mozjpeg");
1586
+ id_direct = rb_intern("direct");
1587
+ id_nogvl = rb_intern("nogvl");
1588
+ id_offload = rb_intern("offload");
1589
+ id_auto = rb_intern("auto");
1590
+ id_bytes = rb_intern("bytes");
1591
+ id_path = rb_intern("path");
1592
+ id_io_buffer = rb_intern("io_buffer");
1593
+ id_return_string = rb_intern("return_string");
1594
+ id_configuration = rb_intern("configuration");
1595
+ id_direct_input_threshold = rb_intern("direct_input_threshold");
1596
+ id_direct_pixel_threshold = rb_intern("direct_pixel_threshold");
1597
+ id_max_pixels = rb_intern("max_pixels");
1598
+ id_max_width = rb_intern("max_width");
1599
+ id_max_height = rb_intern("max_height");
1600
+ id_max_output_size = rb_intern("max_output_size");
1601
+
1602
+ rb_mImagePack = rb_define_module("ImagePack");
1603
+ rb_eImagePackError = rb_const_get(rb_mImagePack, rb_intern("Error"));
1604
+ rb_eImagePackInvalidArgumentError =
1605
+ rb_const_get(rb_mImagePack, rb_intern("InvalidArgumentError"));
1606
+ rb_eImagePackInvalidImageError = rb_const_get(rb_mImagePack, rb_intern("InvalidImageError"));
1607
+ rb_eImagePackUnsupportedError = rb_const_get(rb_mImagePack, rb_intern("UnsupportedError"));
1608
+ rb_eImagePackLimitExceededError = rb_const_get(rb_mImagePack, rb_intern("LimitExceededError"));
1609
+ rb_eImagePackEncodeError = rb_const_get(rb_mImagePack, rb_intern("EncodeError"));
1610
+ rb_eImagePackQualityConstraintError =
1611
+ rb_const_get(rb_mImagePack, rb_intern("QualityConstraintError"));
1612
+ rb_eImagePackOutOfMemoryError = rb_const_get(rb_mImagePack, rb_intern("OutOfMemoryError"));
1613
+ rb_eImagePackCancelledError = rb_const_get(rb_mImagePack, rb_intern("CancelledError"));
1614
+
1615
+ rb_define_singleton_method(rb_mImagePack, "__compress_jpeg", ip_compress_jpeg_entry, 13);
1616
+ rb_define_singleton_method(rb_mImagePack, "__compress_pixels", ip_compress_pixels_entry, 12);
1617
+ rb_define_singleton_method(rb_mImagePack, "__inspect_image", ip_inspect_image_entry, 2);
1618
+ }