How I Improved The Performance Of PicGenie By 75%

PicGenie is my personal project, an AI-powered image generator software. It operates by allowing users to input a prompt and specify the number of images they wish to generate. The system then processes this input to create images, which are subsequently stored in the user's profile for future use.

How Does PicGenie Generates The Image?

PicGenie seamlessly integrates with the Stability API, which handles the image generation. Upon receiving user requests for prompts and desired numbers of images, PicGenie's Node.js and Express.js backend rigorously validates the inputs before forwarding them to the Stability API. The Stability API promptly responds with an array of objects, each containing a base64 string representing an image. These base64 strings are then efficiently processed by PicGenie to retrieve the corresponding images.

Subsequently, PicGenie stores the generated images and promptly transmits them back to the client, powered by Next.js, for display within the user interface. This end-to-end process ensures a reliable and streamlined experience for users, from request submission to image presentation.

How Did I Improve PicGenie Performance By 75%?

After encountering significant performance issues due to the lengthy base64 strings received from the Stability API, I decided to find a viable solution. Consulting with fellow engineers yielded various attempts to mitigate the problem, but none proved satisfactory, often resulting in timeouts on the client side.

Subsequently, I delved into the cloud hosting providers and found Cloudinary to be a promising solution. Reimagining my application's infrastructure, I reconfigured it to leverage Cloudinary for image hosting, a decision that ultimately transformed the performance of my application.

In this revamped approach, upon receiving base64 strings from the Stability API, I created an "imageUrls" array. Iterating through the image responses, I systematically uploaded each base64 image to Cloudinary for hosting. Simultaneously, I created a new MongoDB document to store the relevant image information, with the Cloudinary URL serving as the definitive image reference.

As part of this process, I pushed the newly acquired Cloudinary URLs to the "imageUrls" array. Additionally, I updated the User document to incorporate the Cloudinary image URL. Finally, I promptly responded to the client with the populated "imageUrls" array, which resulted in a seamless and efficient image handling process.

const generateImage = async (request, response) => {
  const { prompt, number } = request.body;
  const { _id } = request.user;

  if (!STABILITY_API_KEY) {
    throw new Error("Missing Stability API key.");
  }

  const requestBody = {
    text_prompts: [
      {
        text: prompt,
      },
    ],
    cfg_scale: 4,
    height: 1024,
    width: 1024,
    steps: 30,
    samples: number,
  };

  const requestOptions = {
    headers: {
      "Content-Type": "application/json",
      Accept: "application/json",
      Authorization: `Bearer ${STABILITY_API_KEY}`,
    },
  };

  const aiResponse = await axios.post(
    `${STABILITY_API_HOST}/v1/generation/${STABILITY_ENGINE_ID}/text-to-image`,
    requestBody,
    requestOptions
  );

  const responseData = aiResponse.data;

  const user = await User.findById(_id);

  const imagesResponse = responseData.artifacts.filter(
    (image) => image.finishReason === "SUCCESS"
  );

  const imageUrls = [];

  for (const image of imagesResponse) {
    const imageStr = `data:image/png;base64,${image.base64}`;
    const result = await cloudinary.uploader.upload(imageStr, {
      folder: "image",
      width: 1024,
      height: 1024,
      crop: "fill",
    });

    const newImage = new Image({
      imageUrl: result.secure_url,
      userId: _id,
      created_at: new Date().getTime(),
      prompt: prompt,
    });
    const savedImage = await newImage.save();

    imageUrls.push(result.secure_url);
    user.images.push(savedImage._id);
    await user.save();
  }

  await User.findByIdAndUpdate(_id, {
    $inc: { creditsLeft: -number },
  });

  response.json(imageUrls);
};

This approach significantly improved efficiency by reducing the workload when retrieving user images for their profile page. Instead of handling numerous lengthy base64 strings, the application now simply retrieves image URLs, resulting in faster performance.

By storing image URLs in the database and leveraging Cloudinary for image hosting, the process of fetching user images has been streamlined. This means that when users visit their profile page, the application retrieves and displays image URLs directly in the user interface, eliminating the need to process and transmit large amounts of data. Overall, this optimization has enhanced the user experience by ensuring quicker loading times and smoother navigation within the application.

const fetchImage = async (request, response) => {
  const { _id } = request.user;

  const user = await User.findById(_id).populate("images");

  const images = user.images;
  response.json(images);
};

I hope you found this blog post helpful! If you're interested in trying out PicGenie, you can find it at this link: PicGenie. Don't forget to subscribe to my newsletter for more technical blogs like this one. Thanks for reading!