image2sand-init.js 9.8 KB

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