image2sand-init.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287
  1. /**
  2. * Image2Sand (https://github.com/orionwc/Image2Sand) Initialization
  3. *
  4. * This script handles the initialization of the Image2Sand converter.
  5. *
  6. */
  7. // Global variables for the image converter
  8. let originalImage = null;
  9. let fileName = '';
  10. let convertedCoordinates = null;
  11. let currentImageData = null;
  12. /**
  13. * Open the image converter dialog with the selected image
  14. * @param {File} file - The image file to convert
  15. */
  16. function openImageConverter(file) {
  17. if (!file) {
  18. logMessage('No file selected for conversion.', LOG_TYPE.ERROR);
  19. return;
  20. }
  21. // Check if the file is an image
  22. if (!file.type.startsWith('image/')) {
  23. // If not an image, let the original uploadThetaRho handle it
  24. return;
  25. }
  26. fileName = file.name.split('.')[0]; // Remove extension
  27. // Create an image element to load the file
  28. const img = new Image();
  29. img.onload = function() {
  30. // Draw the image on the canvas
  31. originalImage = img;
  32. drawAndPrepImage(img);
  33. // Show the converter dialog
  34. const overlay = document.getElementById('image-converter');
  35. overlay.classList.remove('hidden');
  36. overlay.classList.add('visible');
  37. // Initialize the UI elements
  38. initializeUI();
  39. // Convert the image with default settings
  40. convertImage();
  41. };
  42. img.onerror = function() {
  43. logMessage(`Failed to load image: ${file.name}`, LOG_TYPE.ERROR);
  44. };
  45. // Load the image from the file
  46. img.src = URL.createObjectURL(file);
  47. }
  48. /**
  49. * Initialize UI elements for the image converter
  50. */
  51. function initializeUI() {
  52. // Set up event listeners for UI controls
  53. const epsilonSlider = document.getElementById('epsilon-slider');
  54. const epsilonValueDisplay = document.getElementById('epsilon-value-display');
  55. epsilonSlider.addEventListener('input', function() {
  56. epsilonValueDisplay.textContent = this.value;
  57. });
  58. // Set up event listeners for other controls
  59. //document.getElementById('epsilon-slider').addEventListener('change', convertImage);
  60. //document.getElementById('dot-number').addEventListener('change', convertImage);
  61. //document.getElementById('contour-mode').addEventListener('change', convertImage);
  62. //document.getElementById('is-loop').addEventListener('change', convertImage);
  63. //document.getElementById('no-shortcuts').addEventListener('change', convertImage);
  64. }
  65. /**
  66. * Save the converted pattern as a .thr file
  67. */
  68. async function saveConvertedPattern() {
  69. convertedCoordinates = document.getElementById('polar-coordinates-textarea').value;
  70. if (!convertedCoordinates) {
  71. logMessage('No converted coordinates to save.', LOG_TYPE.ERROR);
  72. return;
  73. }
  74. try {
  75. // Create a safe filename (replace spaces and special characters)
  76. const safeFileName = fileName.replace(/[^a-z0-9]/gi, '_').toLowerCase();
  77. const thrFileName = `${safeFileName}.thr`;
  78. // Create a Blob with the coordinates
  79. const blob = new Blob([convertedCoordinates], { type: 'text/plain' });
  80. // Create a FormData object
  81. const formData = new FormData();
  82. formData.append('file', new File([blob], thrFileName, { type: 'text/plain' }));
  83. // Show processing indicator
  84. const processingIndicator = document.getElementById('processing-status');
  85. const processingMessage = document.getElementById('processing-message');
  86. if (processingMessage) {
  87. processingMessage.textContent = `Saving pattern as ${thrFileName}...`;
  88. }
  89. processingIndicator.classList.add('visible');
  90. // Upload the file
  91. const response = await fetch('/upload_theta_rho', {
  92. method: 'POST',
  93. body: formData
  94. });
  95. const result = await response.json();
  96. if (result.success) {
  97. const fileInput = document.getElementById('upload_file');
  98. const finalFileName = 'custom_patterns/' + thrFileName;
  99. logMessage(`Image converted and saved as ${finalFileName}`, LOG_TYPE.SUCCESS);
  100. // Close the converter dialog
  101. closeImageConverter();
  102. // clear the file input
  103. fileInput.value = '';
  104. // Refresh the file list
  105. await loadThetaRhoFiles();
  106. // Select the newly created file
  107. const fileList = document.getElementById('theta_rho_files');
  108. const listItems = fileList.querySelectorAll('li');
  109. for (const item of listItems) {
  110. if (item.textContent === finalFileName) {
  111. selectFile(finalFileName, item);
  112. break;
  113. }
  114. }
  115. } else {
  116. logMessage(`Failed to save pattern: ${result.error || 'Unknown error'}`, LOG_TYPE.ERROR);
  117. }
  118. } catch (error) {
  119. logMessage(`Error saving pattern: ${error.message}`, LOG_TYPE.ERROR);
  120. } finally {
  121. // Hide processing indicator
  122. document.getElementById('processing-status').classList.remove('visible');
  123. }
  124. }
  125. /**
  126. * Clear a canvas
  127. * @param {string} canvasId - The ID of the canvas element to clear
  128. */
  129. function clearCanvas(canvasId) {
  130. const canvas = document.getElementById(canvasId);
  131. const ctx = canvas.getContext('2d');
  132. ctx.clearRect(0, 0, canvas.width, canvas.height);
  133. }
  134. /**
  135. * Close the image converter dialog
  136. */
  137. function closeImageConverter() {
  138. const overlay = document.getElementById('image-converter');
  139. overlay.classList.remove('visible');
  140. overlay.classList.add('hidden');
  141. // Clear the canvases
  142. clearCanvas('original-image');
  143. clearCanvas('edge-image');
  144. clearCanvas('dot-image');
  145. clearCanvas('connect-image');
  146. // Reset variables
  147. originalImage = null;
  148. fileName = '';
  149. convertedCoordinates = null;
  150. currentImageData = null;
  151. // Disable the save button
  152. //document.getElementById('save-pattern-button').disabled = true;
  153. }
  154. async function generateOpenAIImage(apiKey, prompt) {
  155. if (isGeneratingImage) {
  156. logMessage("Image is still generating - please don't press the button.", LOG_TYPE.INFO);
  157. } else {
  158. isGeneratingImage = true;
  159. clearCanvas('original-image');
  160. clearCanvas('edge-image');
  161. clearCanvas('dot-image');
  162. clearCanvas('connect-image');
  163. document.getElementById('gen-image-button').disabled = true;
  164. // Show processing indicator
  165. const processingIndicator = document.getElementById('processing-status');
  166. const processingMessage = document.getElementById('processing-message');
  167. if (processingMessage) {
  168. processingMessage.textContent = `Generating image...`;
  169. }
  170. processingIndicator.classList.add('visible');
  171. try {
  172. const fullPrompt = `Draw an image of the following: ${prompt}. But make it a simple black silhouette on a white background, with very minimal detail and no additional content in the image, so I can use it for a computer icon.`;
  173. const response = await fetch('https://api.openai.com/v1/images/generations', {
  174. method: 'POST',
  175. headers: {
  176. 'Authorization': `Bearer ${apiKey}`,
  177. 'Content-Type': 'application/json'
  178. },
  179. body: JSON.stringify({
  180. model: 'dall-e-3',
  181. prompt: fullPrompt,
  182. size: '1024x1024',
  183. quality: 'standard',
  184. response_format: 'b64_json', // Specify base64 encoding
  185. n: 1
  186. })
  187. });
  188. const data = await response.json();
  189. //const imageUrl = data.data[0].url;
  190. if ('error' in data) {
  191. throw new Error(data.error.message);
  192. }
  193. const imageData = data.data[0].b64_json;
  194. //console.log("Image Data: ", imageData);
  195. const imgElement = new Image();
  196. imgElement.onload = function() {
  197. // Draw the image on the canvas
  198. originalImage = imgElement;
  199. drawAndPrepImage(imgElement);
  200. // Convert the image with default settings
  201. convertImage();
  202. };
  203. imgElement.src = `data:image/png;base64,${imageData}`;
  204. //console.log(`Image generated successfully`);
  205. logMessage('Image generated successfully', LOG_TYPE.SUCCESS);
  206. } catch (error) {
  207. //console.error('Image generation error:', error);
  208. logMessage('Image generation error: ' + error, LOG_TYPE.ERROR);
  209. }
  210. isGeneratingImage = false;
  211. document.getElementById('gen-image-button').disabled = false;
  212. document.getElementById('processing-status').classList.remove('visible');
  213. }
  214. }
  215. // Override the uploadThetaRho function to handle image files
  216. const originalUploadThetaRho = window.uploadThetaRho;
  217. window.uploadThetaRho = async function() {
  218. const fileInput = document.getElementById('upload_file');
  219. const file = fileInput.files[0];
  220. if (!file) {
  221. logMessage('No file selected for upload.', LOG_TYPE.ERROR);
  222. return;
  223. }
  224. // Check if the file is an image
  225. if (file.type.startsWith('image/')) {
  226. // Handle image files with the converter
  227. openImageConverter(file);
  228. return;
  229. }
  230. // For non-image files, use the original function
  231. await originalUploadThetaRho();
  232. };
  233. // Remove existing event listener and add a new one
  234. document.getElementById('gen-image-button').addEventListener('click', function() {
  235. let apiKey = document.getElementById('api-key').value;
  236. const prompt = document.getElementById('prompt').value + (document.getElementById('googly-eyes').checked ? ' with disproportionately large googly eyes' : '');
  237. // Show the converter dialog
  238. const overlay = document.getElementById('image-converter');
  239. overlay.classList.remove('hidden');
  240. // Initialize the UI elements
  241. initializeUI();
  242. generateOpenAIImage(apiKey, prompt);
  243. });