operations.cc 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475
  1. // Copyright 2013 Lovell Fuller and others.
  2. // SPDX-License-Identifier: Apache-2.0
  3. #include <algorithm>
  4. #include <functional>
  5. #include <memory>
  6. #include <tuple>
  7. #include <vector>
  8. #include <vips/vips8>
  9. #include "common.h"
  10. #include "operations.h"
  11. using vips::VImage;
  12. using vips::VError;
  13. namespace sharp {
  14. /*
  15. * Tint an image using the provided RGB.
  16. */
  17. VImage Tint(VImage image, std::vector<double> const tint) {
  18. std::vector<double> const tintLab = (VImage::black(1, 1) + tint)
  19. .colourspace(VIPS_INTERPRETATION_LAB, VImage::option()->set("source_space", VIPS_INTERPRETATION_sRGB))
  20. .getpoint(0, 0);
  21. // LAB identity function
  22. VImage identityLab = VImage::identity(VImage::option()->set("bands", 3))
  23. .colourspace(VIPS_INTERPRETATION_LAB, VImage::option()->set("source_space", VIPS_INTERPRETATION_sRGB));
  24. // Scale luminance range, 0.0 to 1.0
  25. VImage l = identityLab[0] / 100;
  26. // Weighting functions
  27. VImage weightL = 1.0 - 4.0 * ((l - 0.5) * (l - 0.5));
  28. VImage weightAB = (weightL * tintLab).extract_band(1, VImage::option()->set("n", 2));
  29. identityLab = identityLab[0].bandjoin(weightAB);
  30. // Convert lookup table to sRGB
  31. VImage lut = identityLab.colourspace(VIPS_INTERPRETATION_sRGB,
  32. VImage::option()->set("source_space", VIPS_INTERPRETATION_LAB));
  33. // Original colourspace
  34. VipsInterpretation typeBeforeTint = image.interpretation();
  35. if (typeBeforeTint == VIPS_INTERPRETATION_RGB) {
  36. typeBeforeTint = VIPS_INTERPRETATION_sRGB;
  37. }
  38. // Apply lookup table
  39. if (HasAlpha(image)) {
  40. VImage alpha = image[image.bands() - 1];
  41. image = RemoveAlpha(image)
  42. .colourspace(VIPS_INTERPRETATION_B_W)
  43. .maplut(lut)
  44. .colourspace(typeBeforeTint)
  45. .bandjoin(alpha);
  46. } else {
  47. image = image
  48. .colourspace(VIPS_INTERPRETATION_B_W)
  49. .maplut(lut)
  50. .colourspace(typeBeforeTint);
  51. }
  52. return image;
  53. }
  54. /*
  55. * Stretch luminance to cover full dynamic range.
  56. */
  57. VImage Normalise(VImage image, int const lower, int const upper) {
  58. // Get original colourspace
  59. VipsInterpretation typeBeforeNormalize = image.interpretation();
  60. if (typeBeforeNormalize == VIPS_INTERPRETATION_RGB) {
  61. typeBeforeNormalize = VIPS_INTERPRETATION_sRGB;
  62. }
  63. // Convert to LAB colourspace
  64. VImage lab = image.colourspace(VIPS_INTERPRETATION_LAB);
  65. // Extract luminance
  66. VImage luminance = lab[0];
  67. // Find luminance range
  68. int const min = lower == 0 ? luminance.min() : luminance.percent(lower);
  69. int const max = upper == 100 ? luminance.max() : luminance.percent(upper);
  70. if (std::abs(max - min) > 1) {
  71. // Extract chroma
  72. VImage chroma = lab.extract_band(1, VImage::option()->set("n", 2));
  73. // Calculate multiplication factor and addition
  74. double f = 100.0 / (max - min);
  75. double a = -(min * f);
  76. // Scale luminance, join to chroma, convert back to original colourspace
  77. VImage normalized = luminance.linear(f, a).bandjoin(chroma).colourspace(typeBeforeNormalize);
  78. // Attach original alpha channel, if any
  79. if (HasAlpha(image)) {
  80. // Extract original alpha channel
  81. VImage alpha = image[image.bands() - 1];
  82. // Join alpha channel to normalised image
  83. return normalized.bandjoin(alpha);
  84. } else {
  85. return normalized;
  86. }
  87. }
  88. return image;
  89. }
  90. /*
  91. * Contrast limiting adapative histogram equalization (CLAHE)
  92. */
  93. VImage Clahe(VImage image, int const width, int const height, int const maxSlope) {
  94. return image.hist_local(width, height, VImage::option()->set("max_slope", maxSlope));
  95. }
  96. /*
  97. * Gamma encoding/decoding
  98. */
  99. VImage Gamma(VImage image, double const exponent) {
  100. if (HasAlpha(image)) {
  101. // Separate alpha channel
  102. VImage alpha = image[image.bands() - 1];
  103. return RemoveAlpha(image).gamma(VImage::option()->set("exponent", exponent)).bandjoin(alpha);
  104. } else {
  105. return image.gamma(VImage::option()->set("exponent", exponent));
  106. }
  107. }
  108. /*
  109. * Flatten image to remove alpha channel
  110. */
  111. VImage Flatten(VImage image, std::vector<double> flattenBackground) {
  112. double const multiplier = sharp::Is16Bit(image.interpretation()) ? 256.0 : 1.0;
  113. std::vector<double> background {
  114. flattenBackground[0] * multiplier,
  115. flattenBackground[1] * multiplier,
  116. flattenBackground[2] * multiplier
  117. };
  118. return image.flatten(VImage::option()->set("background", background));
  119. }
  120. /**
  121. * Produce the "negative" of the image.
  122. */
  123. VImage Negate(VImage image, bool const negateAlpha) {
  124. if (HasAlpha(image) && !negateAlpha) {
  125. // Separate alpha channel
  126. VImage alpha = image[image.bands() - 1];
  127. return RemoveAlpha(image).invert().bandjoin(alpha);
  128. } else {
  129. return image.invert();
  130. }
  131. }
  132. /*
  133. * Gaussian blur. Use sigma of -1.0 for fast blur.
  134. */
  135. VImage Blur(VImage image, double const sigma, VipsPrecision precision, double const minAmpl) {
  136. if (sigma == -1.0) {
  137. // Fast, mild blur - averages neighbouring pixels
  138. VImage blur = VImage::new_matrixv(3, 3,
  139. 1.0, 1.0, 1.0,
  140. 1.0, 1.0, 1.0,
  141. 1.0, 1.0, 1.0);
  142. blur.set("scale", 9.0);
  143. return image.conv(blur);
  144. } else {
  145. // Slower, accurate Gaussian blur
  146. return StaySequential(image).gaussblur(sigma, VImage::option()
  147. ->set("precision", precision)
  148. ->set("min_ampl", minAmpl));
  149. }
  150. }
  151. /*
  152. * Convolution with a kernel.
  153. */
  154. VImage Convolve(VImage image, int const width, int const height,
  155. double const scale, double const offset,
  156. std::vector<double> const &kernel_v
  157. ) {
  158. VImage kernel = VImage::new_from_memory(
  159. static_cast<void*>(const_cast<double*>(kernel_v.data())),
  160. width * height * sizeof(double),
  161. width,
  162. height,
  163. 1,
  164. VIPS_FORMAT_DOUBLE);
  165. kernel.set("scale", scale);
  166. kernel.set("offset", offset);
  167. return image.conv(kernel);
  168. }
  169. /*
  170. * Recomb with a Matrix of the given bands/channel size.
  171. * Eg. RGB will be a 3x3 matrix.
  172. */
  173. VImage Recomb(VImage image, std::vector<double> const& matrix) {
  174. double* m = const_cast<double*>(matrix.data());
  175. image = image.colourspace(VIPS_INTERPRETATION_sRGB);
  176. if (matrix.size() == 9) {
  177. return image
  178. .recomb(image.bands() == 3
  179. ? VImage::new_matrix(3, 3, m, 9)
  180. : VImage::new_matrixv(4, 4,
  181. m[0], m[1], m[2], 0.0,
  182. m[3], m[4], m[5], 0.0,
  183. m[6], m[7], m[8], 0.0,
  184. 0.0, 0.0, 0.0, 1.0));
  185. } else {
  186. return image.recomb(VImage::new_matrix(4, 4, m, 16));
  187. }
  188. }
  189. VImage Modulate(VImage image, double const brightness, double const saturation,
  190. int const hue, double const lightness) {
  191. VipsInterpretation colourspaceBeforeModulate = image.interpretation();
  192. if (HasAlpha(image)) {
  193. // Separate alpha channel
  194. VImage alpha = image[image.bands() - 1];
  195. return RemoveAlpha(image)
  196. .colourspace(VIPS_INTERPRETATION_LCH)
  197. .linear(
  198. { brightness, saturation, 1},
  199. { lightness, 0.0, static_cast<double>(hue) }
  200. )
  201. .colourspace(colourspaceBeforeModulate)
  202. .bandjoin(alpha);
  203. } else {
  204. return image
  205. .colourspace(VIPS_INTERPRETATION_LCH)
  206. .linear(
  207. { brightness, saturation, 1 },
  208. { lightness, 0.0, static_cast<double>(hue) }
  209. )
  210. .colourspace(colourspaceBeforeModulate);
  211. }
  212. }
  213. /*
  214. * Sharpen flat and jagged areas. Use sigma of -1.0 for fast sharpen.
  215. */
  216. VImage Sharpen(VImage image, double const sigma, double const m1, double const m2,
  217. double const x1, double const y2, double const y3) {
  218. if (sigma == -1.0) {
  219. // Fast, mild sharpen
  220. VImage sharpen = VImage::new_matrixv(3, 3,
  221. -1.0, -1.0, -1.0,
  222. -1.0, 32.0, -1.0,
  223. -1.0, -1.0, -1.0);
  224. sharpen.set("scale", 24.0);
  225. return image.conv(sharpen);
  226. } else {
  227. // Slow, accurate sharpen in LAB colour space, with control over flat vs jagged areas
  228. VipsInterpretation colourspaceBeforeSharpen = image.interpretation();
  229. if (colourspaceBeforeSharpen == VIPS_INTERPRETATION_RGB) {
  230. colourspaceBeforeSharpen = VIPS_INTERPRETATION_sRGB;
  231. }
  232. return image
  233. .sharpen(VImage::option()
  234. ->set("sigma", sigma)
  235. ->set("m1", m1)
  236. ->set("m2", m2)
  237. ->set("x1", x1)
  238. ->set("y2", y2)
  239. ->set("y3", y3))
  240. .colourspace(colourspaceBeforeSharpen);
  241. }
  242. }
  243. VImage Threshold(VImage image, double const threshold, bool const thresholdGrayscale) {
  244. if (!thresholdGrayscale) {
  245. return image >= threshold;
  246. }
  247. return image.colourspace(VIPS_INTERPRETATION_B_W) >= threshold;
  248. }
  249. /*
  250. Perform boolean/bitwise operation on image color channels - results in one channel image
  251. */
  252. VImage Bandbool(VImage image, VipsOperationBoolean const boolean) {
  253. image = image.bandbool(boolean);
  254. return image.copy(VImage::option()->set("interpretation", VIPS_INTERPRETATION_B_W));
  255. }
  256. /*
  257. Perform bitwise boolean operation between images
  258. */
  259. VImage Boolean(VImage image, VImage imageR, VipsOperationBoolean const boolean) {
  260. return image.boolean(imageR, boolean);
  261. }
  262. /*
  263. Trim an image
  264. */
  265. VImage Trim(VImage image, std::vector<double> background, double threshold, bool const lineArt) {
  266. if (image.width() < 3 && image.height() < 3) {
  267. throw VError("Image to trim must be at least 3x3 pixels");
  268. }
  269. if (background.size() == 0) {
  270. // Top-left pixel provides the default background colour if none is given
  271. background = image.extract_area(0, 0, 1, 1)(0, 0);
  272. } else if (sharp::Is16Bit(image.interpretation())) {
  273. for (size_t i = 0; i < background.size(); i++) {
  274. background[i] *= 256.0;
  275. }
  276. threshold *= 256.0;
  277. }
  278. std::vector<double> backgroundAlpha({ background.back() });
  279. if (HasAlpha(image)) {
  280. background.pop_back();
  281. } else {
  282. background.resize(image.bands());
  283. }
  284. int left, top, width, height;
  285. left = image.find_trim(&top, &width, &height, VImage::option()
  286. ->set("background", background)
  287. ->set("line_art", lineArt)
  288. ->set("threshold", threshold));
  289. if (HasAlpha(image)) {
  290. // Search alpha channel (A)
  291. int leftA, topA, widthA, heightA;
  292. VImage alpha = image[image.bands() - 1];
  293. leftA = alpha.find_trim(&topA, &widthA, &heightA, VImage::option()
  294. ->set("background", backgroundAlpha)
  295. ->set("line_art", lineArt)
  296. ->set("threshold", threshold));
  297. if (widthA > 0 && heightA > 0) {
  298. if (width > 0 && height > 0) {
  299. // Combined bounding box (B)
  300. int const leftB = std::min(left, leftA);
  301. int const topB = std::min(top, topA);
  302. int const widthB = std::max(left + width, leftA + widthA) - leftB;
  303. int const heightB = std::max(top + height, topA + heightA) - topB;
  304. return image.extract_area(leftB, topB, widthB, heightB);
  305. } else {
  306. // Use alpha only
  307. return image.extract_area(leftA, topA, widthA, heightA);
  308. }
  309. }
  310. }
  311. if (width > 0 && height > 0) {
  312. return image.extract_area(left, top, width, height);
  313. }
  314. return image;
  315. }
  316. /*
  317. * Calculate (a * in + b)
  318. */
  319. VImage Linear(VImage image, std::vector<double> const a, std::vector<double> const b) {
  320. size_t const bands = static_cast<size_t>(image.bands());
  321. if (a.size() > bands) {
  322. throw VError("Band expansion using linear is unsupported");
  323. }
  324. bool const uchar = !Is16Bit(image.interpretation());
  325. if (HasAlpha(image) && a.size() != bands && (a.size() == 1 || a.size() == bands - 1 || bands - 1 == 1)) {
  326. // Separate alpha channel
  327. VImage alpha = image[bands - 1];
  328. return RemoveAlpha(image).linear(a, b, VImage::option()->set("uchar", uchar)).bandjoin(alpha);
  329. } else {
  330. return image.linear(a, b, VImage::option()->set("uchar", uchar));
  331. }
  332. }
  333. /*
  334. * Unflatten
  335. */
  336. VImage Unflatten(VImage image) {
  337. if (HasAlpha(image)) {
  338. VImage alpha = image[image.bands() - 1];
  339. VImage noAlpha = RemoveAlpha(image);
  340. return noAlpha.bandjoin(alpha & (noAlpha.colourspace(VIPS_INTERPRETATION_B_W) < 255));
  341. } else {
  342. return image.bandjoin(image.colourspace(VIPS_INTERPRETATION_B_W) < 255);
  343. }
  344. }
  345. /*
  346. * Ensure the image is in a given colourspace
  347. */
  348. VImage EnsureColourspace(VImage image, VipsInterpretation colourspace) {
  349. if (colourspace != VIPS_INTERPRETATION_LAST && image.interpretation() != colourspace) {
  350. image = image.colourspace(colourspace,
  351. VImage::option()->set("source_space", image.interpretation()));
  352. }
  353. return image;
  354. }
  355. /*
  356. * Split and crop each frame, reassemble, and update pageHeight.
  357. */
  358. VImage CropMultiPage(VImage image, int left, int top, int width, int height,
  359. int nPages, int *pageHeight) {
  360. if (top == 0 && height == *pageHeight) {
  361. // Fast path; no need to adjust the height of the multi-page image
  362. return image.extract_area(left, 0, width, image.height());
  363. } else {
  364. std::vector<VImage> pages;
  365. pages.reserve(nPages);
  366. // Split the image into cropped frames
  367. image = StaySequential(image);
  368. for (int i = 0; i < nPages; i++) {
  369. pages.push_back(
  370. image.extract_area(left, *pageHeight * i + top, width, height));
  371. }
  372. // Reassemble the frames into a tall, thin image
  373. VImage assembled = VImage::arrayjoin(pages,
  374. VImage::option()->set("across", 1));
  375. // Update the page height
  376. *pageHeight = height;
  377. return assembled;
  378. }
  379. }
  380. /*
  381. * Split into frames, embed each frame, reassemble, and update pageHeight.
  382. */
  383. VImage EmbedMultiPage(VImage image, int left, int top, int width, int height,
  384. VipsExtend extendWith, std::vector<double> background, int nPages, int *pageHeight) {
  385. if (top == 0 && height == *pageHeight) {
  386. // Fast path; no need to adjust the height of the multi-page image
  387. return image.embed(left, 0, width, image.height(), VImage::option()
  388. ->set("extend", extendWith)
  389. ->set("background", background));
  390. } else if (left == 0 && width == image.width()) {
  391. // Fast path; no need to adjust the width of the multi-page image
  392. std::vector<VImage> pages;
  393. pages.reserve(nPages);
  394. // Rearrange the tall image into a vertical grid
  395. image = image.grid(*pageHeight, nPages, 1);
  396. // Do the embed on the wide image
  397. image = image.embed(0, top, image.width(), height, VImage::option()
  398. ->set("extend", extendWith)
  399. ->set("background", background));
  400. // Split the wide image into frames
  401. for (int i = 0; i < nPages; i++) {
  402. pages.push_back(
  403. image.extract_area(width * i, 0, width, height));
  404. }
  405. // Reassemble the frames into a tall, thin image
  406. VImage assembled = VImage::arrayjoin(pages,
  407. VImage::option()->set("across", 1));
  408. // Update the page height
  409. *pageHeight = height;
  410. return assembled;
  411. } else {
  412. std::vector<VImage> pages;
  413. pages.reserve(nPages);
  414. // Split the image into frames
  415. for (int i = 0; i < nPages; i++) {
  416. pages.push_back(
  417. image.extract_area(0, *pageHeight * i, image.width(), *pageHeight));
  418. }
  419. // Embed each frame in the target size
  420. for (int i = 0; i < nPages; i++) {
  421. pages[i] = pages[i].embed(left, top, width, height, VImage::option()
  422. ->set("extend", extendWith)
  423. ->set("background", background));
  424. }
  425. // Reassemble the frames into a tall, thin image
  426. VImage assembled = VImage::arrayjoin(pages,
  427. VImage::option()->set("across", 1));
  428. // Update the page height
  429. *pageHeight = height;
  430. return assembled;
  431. }
  432. }
  433. } // namespace sharp