posix.d 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  1. /*
  2. * pixiv_down - CLI-based downloading tool for https://www.pixiv.net.
  3. * Copyright (C) 2024 Mio
  4. *
  5. * This program is free software: you can redistribute it and/or modify
  6. * it under the terms of the GNU General Public License as published by
  7. * the Free Software Foundation, version 3 of the License.
  8. *
  9. * This program is distributed in the hope that it will be useful,
  10. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. * GNU General Public License for more details.
  13. *
  14. * You should have received a copy of the GNU General Public License
  15. * along with this program. If not, see <https://www.gnu.org/licenses/>.
  16. */
  17. module pd.gif_writer.posix;
  18. version(PD_USE_MAGICK):
  19. import core.stdc.errno;
  20. import core.stdc.stdio;
  21. import core.stdc.string;
  22. import std.file;
  23. import std.experimental.logger;
  24. import std.string;
  25. import std.path;
  26. import pd.gif_writer.common;
  27. import pd.imaging.magick;
  28. import pd.image_reader;
  29. public class GIFWriter
  30. {
  31. /**
  32. * Create a GIFWriter.
  33. * Params:
  34. * path = The path at which to write the image data. This object overwrites any data at the specified path.
  35. * count = The number of images you want to include in the image file.
  36. */
  37. this(string path, size_t count)
  38. {
  39. fHandle = null;
  40. fDestinationPath = path;
  41. fTemporaryPath = path ~ ".part";
  42. infof("GIF temporary path = %s", fTemporaryPath);
  43. // Check that we can write to the provided path
  44. FILE *file = fopen(toStringz(fTemporaryPath), "wb");
  45. if (null is file) {
  46. throw new FileException(fTemporaryPath, errno);
  47. }
  48. fclose(file);
  49. }
  50. ~this()
  51. {
  52. dispose(false);
  53. }
  54. void addImage(ImageReader reader, size_t index, GIFFrameProperties properties)
  55. {
  56. if (fDisposed || fFinalised) {
  57. return;
  58. }
  59. /*
  60. * GraphicsMagick will prepend an image if there is no previous
  61. * image in the list when calling 'MagickAddImage', so we keep
  62. * a reference to the first frame so that we can add it after
  63. * the second frame has been added.
  64. */
  65. if (null is fFirstFrame) {
  66. MagickSetImageIndex(reader.getSystemRef(), index);
  67. fFirstFrame = CloneMagickWand(reader.getSystemRef());
  68. return;
  69. }
  70. if (null is fHandle) {
  71. assert(fFirstFrame !is null, "Setting fHandle while fFirstFrame is null");
  72. MagickSetImageIndex(reader.getSystemRef(), index);
  73. fHandle = CloneMagickWand(reader.getSystemRef());
  74. MagickResetIterator(fHandle);
  75. MagickAddImage(fHandle, fFirstFrame);
  76. return;
  77. }
  78. // NOTE: We make a copy here so we don't have to worry about the
  79. // call to ImageReader.dispose in createGIF. This copy is
  80. // destroyed by the call to DestroyMagickWand() in dispose().
  81. MagickWand* otherHandle = CloneMagickWand(reader.getSystemRef());
  82. MagickSetImageIndex(otherHandle, index);
  83. MagickSetImageDelay(otherHandle, properties.delay / 10);
  84. int res = MagickAddImage(fHandle, otherHandle);
  85. if (res == 0) {
  86. // TODO: catch exception. The ExceptionInfo struct needs creating,
  87. // however, we need to check what ImageMagick's version looks like.
  88. errorf("Error adding frame: %s", fromStringz(MagickGetImageFilename(otherHandle)));
  89. }
  90. }
  91. void dispose()
  92. {
  93. dispose(true);
  94. }
  95. bool finalise()
  96. {
  97. if (fFinalised) {
  98. return true;
  99. }
  100. if (fDisposed && !fFinalised) {
  101. errorf("attempting to finalise and already disposed GIFWriter");
  102. return false;
  103. }
  104. // Go back to the start of the list so we correctly write the first frame.
  105. MagickResetIterator(fHandle);
  106. MagickSetImageIterations(fHandle, 0);
  107. MagickSetImageFormat(fHandle, "GIF");
  108. MagickWriteImages(fHandle, toStringz(fTemporaryPath), 1);
  109. // TODO: Error handling. The ExceptionInfo struct needs creating,
  110. // however, we need to check what ImageMagick's version looks like.
  111. rename(fTemporaryPath, fDestinationPath);
  112. fFinalised = true;
  113. return true;
  114. }
  115. private:
  116. MagickWand* fHandle;
  117. MagickWand* fFirstFrame;
  118. bool fDisposed = false;
  119. bool fFinalised = false;
  120. string fDestinationPath;
  121. string fTemporaryPath;
  122. void dispose(bool disposing)
  123. {
  124. if (fDisposed) {
  125. return;
  126. }
  127. if (null !is fHandle) {
  128. DestroyMagickWand(fHandle);
  129. }
  130. fDisposed = true;
  131. }
  132. }