Generative AI – Cosplay Camera App

In this tutorial, we will create an app that uses the Google Gemini Nano Banana to generate a full-body cosplay image from a captured face photo.

In August 2025, Google released Nano Banana (Gemini 2.5 Flash Image), an AI that lets you edit and transform photos with natural language while preserving the features of people and objects. As a fun application, we tried turning face photos into cosplay.

Here is the information of Nano Banana.

https://gemini.google/overview/image-generation

Since Nano Banana cannot be used for free via the API, you’ll need to add a billing method. You can do this in Google AI Studio.

For calling the Google Gemini API, we’ll use Google Apps Script (GAS). The GAS script is written with the help of Google Gemini 2.5 Pro.

What You Will Learn in This Tutorial

  • How to use Nano Banana
  • How to create an API with GAS
  • How to write a GAS script using Gemini
  • How to integrate with the Gemini API

Components Used

Button, TextBox, Canvas, Image, HorizontalArrangement, VerticalArrangement, Web, Camera, Sharing, ImageBase641

Blocks Used

Global Variable, if then else, Dictionaries

You can download the source code (aia file) from the download section at the bottom of the page.

Setting up Google Apps Script

See the Generative AI – Doodle Coach App for instructions on obtaining an API key and deploying Google Apps Script.

Code.gs

function doPost(e) {
  // Main handler for POST requests from App Inventor.
  // Returns a JSON string with: { status, message, prompt, imageData }

  let response = {
    status: 'error',
    message: 'An unknown error occurred.',
    imageData: null
  };

  try {
    // Validate and parse incoming JSON payload
    if (!e || !e.postData || !e.postData.contents) {
      throw new Error('Invalid request: No data received.');
    }
    const jsonData = JSON.parse(e.postData.contents);

    // Expecting base64 image data and optional character/prompt fields
    const base64Data = jsonData.imageData;
    const character = jsonData.character; 
    const prompt =
      jsonData.prompt ||
      `Convert this into a full-body photo, do not modify the face at all, and apply cosplay as ${character}. No textual reply is needed.`;

    // Basic input validation
    if (!base64Data || typeof base64Data !== 'string') {
      throw new Error('Invalid request: imageData is missing or not a string.');
    }

    // Remove data URL header (e.g., "data:image/png;base64,")
    const cleanedBase64 = cleanBase64String(base64Data);

    // Read API key from Script Properties
    const apiKey = PropertiesService.getScriptProperties().getProperty('GEMINI_API_KEY');
    if (!apiKey) {
      throw new Error('Configuration error: API key is not set in Script Properties.');
    }

    // Call Gemini image-generation endpoint
    const generatedBase64 = callGeminiApi(apiKey, cleanedBase64, prompt);

    // Success response
    response = {
      status: 'success',
      message: 'Image generated successfully.',
      prompt: prompt,
      imageData: generatedBase64
    };

  } catch (error) {
    // Failure response
    console.error(error.toString());
    response.message = error.message;
  }

  // Return JSON output
  return ContentService.createTextOutput(JSON.stringify(response))
    .setMimeType(ContentService.MimeType.JSON);
}

function cleanBase64String(base64String) {
  // Strip a leading data URL header like "data:image/png;base64,"
  return base64String.replace(/^data:image\/[a-z]+;base64,/, '');
}

function callGeminiApi(apiKey, base64Image, prompt) {
  // Invoke the Gemini image-generation model with an image + instruction text
  const model = 'gemini-2.5-flash-image-preview';
  const url = `https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent?key=${apiKey}`;

  const payload = {
    contents: [{
      parts: [
        { text: prompt },
        {
          inlineData: {
            mimeType: 'image/png', // App Inventor Canvas exports PNG
            data: base64Image
          }
        }
      ]
    }],
    generationConfig: {
      // Request both image and text modalities in the response
      responseModalities: ['IMAGE', 'TEXT']
    }
  };

  const options = {
    method: 'post',
    contentType: 'application/json',
    payload: JSON.stringify(payload),
    muteHttpExceptions: true // Capture non-2xx responses instead of throwing
  };

  const response = UrlFetchApp.fetch(url, options);
  const responseCode = response.getResponseCode();
  const responseBody = response.getContentText();

  if (responseCode === 200) {
    const jsonResponse = JSON.parse(responseBody);

    // Find the image part in the response
    const imagePart = jsonResponse?.candidates?.[0]?.content?.parts?.find(p => p.inlineData);

    if (imagePart && imagePart.inlineData.data) {
      // Return base64 image data
      return imagePart.inlineData.data;
    } else {
      // If no image, surface any returned text as the error message
      console.error('API response did not contain image data. Response: ' + responseBody);
      const textPart = jsonResponse?.candidates?.[0]?.content?.parts?.find(p => p.text);
      const errorMessage = textPart ? textPart.text : 'No image data in response.';
      throw new Error(`API Error: ${errorMessage}`);
    }
  } else {
    // Non-2xx HTTP response
    console.error('API Error. Code: ' + responseCode + '. Body: ' + responseBody);
    throw new Error(`API request failed with status code ${responseCode}. Details: ${responseBody}`);
  }
}

App Inventor App

From the [Projects] menu, select [Start new project] and name it “CosplayCamera“.

Designer

You need to be logged in to view the rest of the content. Please . Not a Member? Join Us