How to create imageVariation from client-side application JavaScript?

I am working with browser based application and wanted to create imageVariation from imageData or image. Now issue is that all examples in the documentation work with node.js otherwise you get an error. I tried so many things. First issue is that you can only send formData(). In formData() you can only send image as a file. What if you want to send image as a buffer? Then how you can send it using formData(). Is there any other way of doing this on client-side?

Note: I cannot use any node.js library such as ‘fs’ or ‘sharp’ because of E6S support. Therefore please do not give me example of that. I already wasted 2 weeks.

   const form_data = new FormData();
    
    //Error here because you can only send image file. What if I wanted to send in- 
    //memory image. What will be type of in formData() then? What if we just want 
   //to send an image but not as file? 

    form_data.append("image", iimage);
    form_data.append("n",1);
    form_data.append("size",'1024x1024');
async function createVariation(formData) {
  try {
    const response = await fetch("https://api.openai.com/v1/images/variations", {
      method: "POST",
      body: formData,
      headers: {"Authorization": "Bearer" + " " + api_key}
    });
    const result = await response.json();
    console.log("Success:", result);
  } catch (error) {
    console.error("Error:", error);
  }
}

How are you getting your ‘iimage’ object loaded? You will need to append the actual bytes exactly as they would exist on disk for an image. That is, the exact bytes of the image, in whatever format.

Any code that uploads files via HTTP POSTS will be working the same exact way OpenAI works, so you don’t need to actually even look specifically at OpenAI docs to learn how to do this.

I’d actually ask OpenAI to generate the code too! Just describe in a lot of detail what you need to do and it can probably show you the code, for how to upload the file data via code.

1 Like

Yes, image exists on the disk but I uploaded the image on browser and then preprocess the image and as a result of preprocessing, I am getting ImageData. Then I convert imageData into .png image using this code for conversion.

const iimage = imagedata_to_image(dataNew);

function imagedata_to_image(imagedata) {
var canvas = document.createElement("canvas");
canvas.width = 512;
canvas.height = 512;
var ctx = canvas.getContext("2d");
ctx.putImageData(imagedata, 0, 0);
var dataURL = canvas.toDataURL("image/png");
var img = document.createElement("img");
img.src = canvas.toDataURL("image/png");
return img;
}

After that to send image as a file.

form_data.append("image",new File([iimage], "test1.png", { type: "image/png" }));

After this, I am getting an error “‘invalid_image_format’, message: 'Uploaded image must be a PNG and less than 4 MB.”

I also converted ImageData to blob and tried to append it and getting the same error. This is the closest I get to what I wanted to do. It is written in the documentation that we can also use buffer but that is not working.

What’s the purpose of all that code for image processing? Are you just wanting to get an image off your hard drive, and send it to OpenAI? …or are you doing some processing of the image before sending it?

I am preprocessing to get a mask image. Actually I wanted to use it in ImageEdit. However, I am just testing it in a variation because if it work with variation, it will also work with ImageEdit. Secondly, if user upload image of any size, then you also need to resize it according to requirement.

If this basic way of uploading image data works (link below), but your processed image data doesn’t work, that indicates that your image processing stuff is not generating valid binary data, meaning, not putting out into PNG formated data.

EDIT: Btw you’re returning an ELEMENT object from that method above, not an image, or any image data.

Yes, I am using similar way and my processed image is showing on the canvas correctly if I append it like this.

document.body.appendChild(img);

However, may be you are right that it is not generating valid binary data. I will look into it to correct it. Somebody also mentioned of converting image to b64. Thanks for pointing it out.

You’ll essentially need to create the png file format in-memory.

You can use node.js if you are “doing API” the right way - by not putting code in the client (containing your API key waiting to be stolen) that directly contacts OpenAI from the client, but instead connects through your own backend.

To continue, we can ask a bot the right questions for you… (and you can check its work)

Yes, your client is on the right track with using the HTML canvas API to convert the raw image data to a PNG. However, the issue is that the toDataURL function returns a data URL which is a base64 encoded string of the image data, not a byte stream.

To get a byte stream, you can use the canvas.toBlob function which creates a Blob object representing the image data in the specified format. Here’s an example:

function imagedata_to_image(imagedata) {
    var canvas = document.createElement("canvas");
    canvas.width = 512;
    canvas.height = 512;
    var ctx = canvas.getContext("2d");
    ctx.putImageData(imagedata, 0, 0);
    
    return new Promise((resolve, reject) => {
        canvas.toBlob((blob) => {
            if (blob) {
                // Blob is your png byte stream
                resolve(blob);
            } else {
                reject(new Error('Image conversion to blob failed'));
            }
        }, 'image/png');
    });
}

The toBlob function is asynchronous and provides a callback function that will be invoked with the resulting Blob object. This function returns a Promise that resolves with the Blob object once the conversion is complete.

You can then use this Blob object to create a FormData object for your API request:

const imageData = imagedata_to_image(dataNew);

imageData.then(blob => {
    var formData = new FormData();
    formData.append('file', blob, 'image.png');

    // Use formData for your API request
    // ...
}).catch(error => {
    console.error('Image conversion failed: ', error);
});

In this example, ‘file’ is the key for the API request, and ‘image.png’ is the filename for the Blob object. You may need to adjust these values based on the API requirements.

The formdata object creates the MIME encoding.

Then more useful bot docs for the other parameters:

Here’s how you can add other fields to the FormData object along with the file:

const imageData = imagedata_to_image(dataNew);

imageData.then(blob => {
    var formData = new FormData();
    formData.append('file', blob, 'image.png');

    // Add other fields to formData
    formData.append('key1', 'value1');
    formData.append('key2', 'value2');

    // Use formData for your API request
    // ...
}).catch(error => {
    console.error('Image conversion failed: ', error);
});

In this example, ‘key1’ and ‘key2’ are the keys for the additional fields, and ‘value1’ and ‘value2’ are their corresponding values. You can replace these with the actual keys and values required by your API.

When you send the formData with an API request, the browser will automatically set the Content-Type header to multipart/form-data and properly encode the form data and file data into the request body.

Here’s an example of how you can send the formData with a POST request using the Fetch API:

fetch('https://example.com/api', {
    method: 'POST',
    body: formData,
})
.then(response => response.json())
.then(data => console.log(data))
.catch((error) => {
    console.error('Error:', error);
});

In this example, replace 'https://example.com/api' with the actual URL of your API endpoint.

@_j is agreeing with me and going the further step of saying HOW to get the actual data which is just ‘canvas.toBlob’, so that should do it.

@_j @wclayf Thanks. I will check it. I hope that it will work. :slight_smile:

1 Like

@_j After converting imageData into blob and appending the way you described, I am now getting an error “image’ is a required property”. This error is due to the fact that we are not sending an image file here.

formData.append('file', blob, 'image.png');

As the key for image file is ‘image’, therefore I also changed the key and check it but getting similar error.

formData.append('image', blob, 'image.png'); or formData.append('image', blob);

I’d paste the the entire relevant code into ChatGPT and say “why am I getting this error” and show the error. I bet you 90% chance it will tell you the problem. :slight_smile:

Or post your entire code for us humans. Just saying you’re getting “similar error” isn’t too much to go on.

I used the code pasted by @_j . The only thing I am using is ImageData. If you pick file from disk, it is okay but when you convert imageData, it is not okay. I think, I need to reformat imageData or .png image bytes to make it valid. However, when I show image converted from ImageData on document, then it is okay.

I asked ChatGPT how to convert a Canvas to a PNG blob and it gave this:

let canvas = document.getElementById('myCanvas'); // get your canvas object
canvas.toBlob(function(blob) {
    // Now you can use blob as the kind of blob object you can use in an upload form
    // For example, you could append it to a FormData object
    let formData = new FormData();
    formData.append('file', blob, 'canvas.png');
    // Now you can send formData to a server
}, 'image/png');

If you’re doing this, and getting an error then there’s something else about your code that is breaking other than the image data, I bet.

EDIT: And also you can do a console log of ‘blob.size’ and ‘blob.type’ to be sure the size and mime are ok.

1 Like

Sorry for late reply. After lot of tries, checking binary data and everything and taking help from ChatGPT, this works.

const ImageDataToBlob = async function (imageData, formData) {
  return new Promise((resolve, reject) => {
    let w = imageData.width;
    let h = imageData.height;
    let canvas = document.createElement("canvas");
    canvas.width = w;
    canvas.height = h;
    let ctx = canvas.getContext("2d");
    ctx.putImageData(imageData, 0, 0);

        // Convert the ImageData to a data URL
    const dataURL = canvas.toDataURL('image/png');

        // Convert the data URL back to a Blob
        fetch(dataURL)
          .then(response => response.blob())
          .then(blob => {
            // Create a File object
           const file = new File([blob], 'canvas.png');
            // Append the File object to the form data
           formData.append('image', file);
           formData.append("n",1);
           formData.append("size",'1024x1024');

            resolve(blob);
          })
          .catch(error => {
            reject(error);
          });
      });
};

Thanks for sharing the code. I asked ChatGPT if there was a way to optimize it so it doesn’t use the fetch and it says this: (I haven’t tested it tho)

Certainly! You can avoid using fetch to convert the data URL to a Blob by using the dataURLtoBlob function directly. Here’s an improved version of the JavaScript code:

const ImageDataToBlob = async function (imageData, formData) {
  return new Promise((resolve, reject) => {
    let w = imageData.width;
    let h = imageData.height;
    let canvas = document.createElement("canvas");
    canvas.width = w;
    canvas.height = h;
    let ctx = canvas.getContext("2d");
    ctx.putImageData(imageData, 0, 0);

    // Convert the ImageData to a data URL
    const dataURL = canvas.toDataURL('image/png');

    // Convert the data URL to a Blob
    const blob = dataURLtoBlob(dataURL);

    // Create a File object
    const file = new File([blob], 'canvas.png');

    // Append the File object to the form data
    formData.append('image', file);
    formData.append("n", 1);
    formData.append("size", '1024x1024');

    resolve(blob);
  });
};

// Helper function to convert dataURL to Blob
function dataURLtoBlob(dataURL) {
  // Decode the dataURL
  const byteString = atob(dataURL.split(',')[1]);

  // Get the MIME type from the dataURL
  const mimeString = dataURL.split(',')[0].split(':')[1].split(';')[0];

  // Construct a Uint8Array from the byteString
  const ab = new ArrayBuffer(byteString.length);
  const ia = new Uint8Array(ab);
  for (let i = 0; i < byteString.length; i++) {
    ia[i] = byteString.charCodeAt(i);
  }

  // Create and return the Blob
  return new Blob([ab], {type: mimeString});
}

This code defines a dataURLtoBlob helper function that converts a data URL directly to a Blob without using fetch. As a result, it’s more efficient and avoids the unnecessary network request that fetch would perform.

Please note that this code still uses async although it doesn’t have any await expressions. If you don’t need to perform any other asynchronous operations, you might consider removing the async keyword and returning the Blob directly. However, if you want to keep the function as an async function for future extension or to maintain API consistency, you can keep it as is.

1 Like