Table of Contents

Generating images

Every image operation on TTMSMCPCloudImageAI goes through one of four calls — Execute, RemoveBackground, ReplaceBackground, and the Images collection that changes what Execute means — and all four end in the same place: a base64 payload delivered to OnImageGenerated, or a failure delivered to OnRequestError. Which call to reach for is decided by how many reference images you have and how specific the instruction is, not by which service you are on: the component presents all five providers through the same surface and substitutes a written instruction where a provider has no dedicated endpoint. This guide covers each call with a working example, what to do with the result, and where a request can fail without the component raising anything.

Text to image

The simplest request has no reference image at all: a prompt goes out, an image comes back. Use it whenever you are creating something rather than changing something.

procedure TForm1.GenerateFromPrompt(const APrompt: string);
begin
  { A leftover reference image turns the next call into an image-to-image
    request, so clear the collection when you want text to image. }
  ImageAI.Images.Clear;

  btnGenerate.Enabled := False;
  memoLog.Lines.Add('Requesting: ' + APrompt);

  { The call returns immediately. Everything else happens in the two
    result events. }
  ImageAI.Execute(APrompt);
end;

Execute returns as soon as the request is dispatched, which means the method that called it finishes before the image exists. Disable whatever control started the request there, and re-enable it in both result events — an error path that leaves a button disabled is the most common way this goes wrong.

Note the Images.Clear call. The collection is not consumed by Execute, so reference images left over from a previous request silently turn the next text-to-image call into an image-to-image one.

Receiving and saving the result

OnImageGenerated hands you the generated image as base64 plus the finished HTTP request. The encoding is the same on every service: where a provider answers with a URL rather than image data, the component downloads it and encodes it before the event fires, so a single decoding path is enough.

procedure TForm1.ImageAIImageGenerated(Sender: TObject;
  ARequestResult: TTMSMCPCloudBaseRequestResult; ABase64Image: string);
var
  Stream: TMemoryStream;
  FileName: string;
begin
  btnGenerate.Enabled := True;

  if ABase64Image = '' then
  begin
    memoLog.Lines.Add('The service returned no image.');
    Exit;
  end;

  Stream := TMemoryStream.Create;
  try
    TTMSMCPUtils.LoadStreamFromBase64(ABase64Image, Stream);

    FileName := TPath.Combine(FOutputFolder,
      FormatDateTime('yyyymmdd-hhnnss', Now) + '.png');
    Stream.SaveToFile(FileName);

    { Rewind before handing the same stream to a picture control. }
    Stream.Position := 0;
    imgResult.Bitmap.LoadFromStream(Stream);
  finally
    Stream.Free;
  end;

  memoLog.Lines.Add('Saved ' + FileName);
end;

procedure TForm1.ImageAIRequestError(Sender: TObject;
  ARequestResult: TTMSMCPCloudBaseRequestResult);
begin
  btnGenerate.Enabled := True;
  memoLog.Lines.Add(Format('Failed (%d): %s',
    [ARequestResult.ResponseCode, ARequestResult.ResultString]));
end;

TTMSMCPUtils.LoadStreamFromBase64 fills a stream from the payload. Rewind it with Position := 0 before handing the same stream to a picture control after saving it — SaveToFile leaves the position at the end.

An empty ABase64Image is possible on a request the transport considered successful, so check for it. The component treats that as a failure and routes it to OnRequestError in most paths, but a defensive check in the success handler costs nothing and makes the distinction visible in the log.

Editing one reference image

Pass a reference image and the call becomes an edit: the prompt now describes the change rather than the subject. Four overloads take the reference in whatever form you already have it — a TStream, a base64 string, or the framework picture type (TBitmap in FMX, TPicture in VCL):

procedure TForm1.EditFromFile(const AFileName, APrompt: string);
var
  Stream: TMemoryStream;
begin
  Stream := TMemoryStream.Create;
  try
    Stream.LoadFromFile(AFileName);

    { The stream is read during the call, so it is safe to free it as soon
      as Execute returns - the request no longer needs it. }
    ImageAI.Execute(Stream, APrompt);
  finally
    Stream.Free;
  end;
end;

procedure TForm1.EditDisplayedImage(const APrompt: string);
begin
  { The framework overload saves the picture to a stream for you: TBitmap
    in FMX, TPicture in VCL. }
  ImageAI.Execute(imgSource.Bitmap, APrompt);
end;

procedure TForm1.EditFromBase64(const ABase64, APrompt: string);
begin
  { A reference that already arrived as base64 - from a previous generation,
    or from a database - needs no decoding of your own. }
  ImageAI.Execute(ABase64, APrompt);
end;

The stream is read during the call — encoded into the request body before Execute returns — so it is safe to free it immediately afterwards. The framework overloads simply save the picture to a temporary stream and call the stream overload, so they behave identically.

Several references with the Images collection

For a composition — several products on one surface, a subject placed into a scene, a style taken from one image and applied to another — populate the Images collection and call Execute with only a prompt. A non-empty collection is what turns that call into a multi-reference request:

procedure TForm1.ComposeFromFiles;
begin
  { Whatever is in Images at the time of the call is sent with the prompt.
    Clear it first so an earlier request does not leak into this one. }
  ImageAI.Images.Clear;

  ImageAI.Images.Add.Stream.LoadFromFile(
    TPath.Combine(FAssetFolder, 'table.jpg'));
  ImageAI.Images.Add.Stream.LoadFromFile(
    TPath.Combine(FAssetFolder, 'book.jpg'));
  ImageAI.Images.Add.Stream.LoadFromFile(
    TPath.Combine(FAssetFolder, 'flowers.jpg'));

  { Execute with only a prompt: because Images is populated, this becomes a
    multi-reference request instead of a text-to-image one. }
  ImageAI.Execute('Create a still life composition from this table, book ' +
    'and flowers, lit from the left.');
end;

procedure TForm1.ComposeFromDisplayedImage;
begin
  ImageAI.Images.Clear;

  { The Add overload takes the framework picture type directly: TBitmap in
    FMX, TPicture in VCL. }
  ImageAI.Images.Add(imgSource.Bitmap);

  ImageAI.Execute('Remove the coffee cup from this picture.');
end;

Each TTMSMCPCloudAIImage owns a TMemoryStream you can load from a file, from another stream, or from a displayed picture through the Add overload. The collection owns the items, so nothing has to be freed by hand; Clear releases them.

How the references are used is the one place the providers genuinely differ. They are sent as multipart parts, inline data, numbered input images, or a reference-image array depending on the service, and each has its own limit on how many it will accept. Two or three references behave predictably everywhere; beyond that, check the provider's own documentation before relying on it.

Removing a background

RemoveBackground takes the same three input forms as Execute and needs no prompt:

procedure TForm1.CutOutProduct(const AFileName: string);
var
  Stream: TMemoryStream;
begin
  Stream := TMemoryStream.Create;
  try
    Stream.LoadFromFile(AFileName);
    ImageAI.RemoveBackground(Stream);
  finally
    Stream.Free;
  end;
end;

procedure TForm1.CutOutDisplayedImage;
begin
  ImageAI.RemoveBackground(imgSource.Bitmap);
end;

procedure TForm1.CutOutWithOwnPrompt(const AFileName: string);
var
  Stream: TMemoryStream;
begin
  { Only some services have a dedicated background endpoint; for the rest
    RemoveBackground sends a generic instruction. When the result is not
    clean enough, drop to Execute and write the instruction yourself. }
  Stream := TMemoryStream.Create;
  try
    Stream.LoadFromFile(AFileName);
    ImageAI.Execute(Stream,
      'Cut the product out of its background and place it on a fully ' +
      'transparent background. Keep the original shadow under the product.');
  finally
    Stream.Free;
  end;
end;

Only some services have a dedicated background endpoint. Stability does, and the component uses it. For the others, RemoveBackground sends the reference image with a generic written instruction — which works well for a clean subject on a plain background and less well for hair, glass, or a busy scene. When the result is not good enough, that is not a limitation to work around: drop to Execute with the same image and write the instruction yourself, which is exactly what the shortcut was doing on your behalf.

Replacing a background

ReplaceBackground takes two images: the subject first, the new background second. Both come in the same three forms.

procedure TForm1.RestageProduct(const ASubjectFile, ABackgroundFile: string);
var
  Subject, Background: TMemoryStream;
begin
  Subject := TMemoryStream.Create;
  try
    Background := TMemoryStream.Create;
    try
      Subject.LoadFromFile(ASubjectFile);
      Background.LoadFromFile(ABackgroundFile);

      { Order is part of the contract: the first image supplies the subject,
        the second supplies the new background. Swapping them produces a
        plausible-looking but wrong result rather than an error. }
      ImageAI.ReplaceBackground(Subject, Background);
    finally
      Background.Free;
    end;
  finally
    Subject.Free;
  end;
end;

procedure TForm1.RestageDisplayedImage;
begin
  ImageAI.ReplaceBackground(imgSubject.Bitmap, imgBackdrop.Bitmap);
end;

The order is part of the contract and is not checked. Swapping the two produces a plausible-looking wrong result — the background restaged against the subject — rather than an error, so it is worth naming the variables for their role as the example does.

As with removal, only Stability has a dedicated endpoint; the others receive both images with a written instruction. Stability's variant is also the one case where the component polls for its result, so a replacement there takes noticeably longer than a generation.

Handling failures and long-running requests

OnRequestError is the single failure path, and it covers three different situations: the HTTP request itself failed, the service returned a response that reported an error, or the response arrived successfully but carried no image. ARequestResult.ResponseCode and ResultString are what separate them.

procedure TForm1.SetUpDiagnostics;
begin
  ImageAI.OnRequestError := ImageAIRequestError;

  { Inherited from the cloud transport base: these fire for the generation
    request and for every poll it may trigger. }
  ImageAI.OnRequestStarted := ImageAIRequestStarted;
  ImageAI.OnRequestProgress := ImageAIRequestProgress;
end;

procedure TForm1.ImageAIRequestStarted(Sender: TObject;
  const ARequestResult: TTMSMCPCloudBaseRequestResult);
begin
  progress.Value := 0;
  lblStatus.Text := 'Sending ' + ARequestResult.Name;
end;

procedure TForm1.ImageAIRequestProgress(Sender: TObject;
  const ARequestResult: TTMSMCPCloudBaseRequestResult; AProgress: Single;
  AUpload: Boolean);
begin
  progress.Value := AProgress;
end;

procedure TForm1.ImageAIRequestError(Sender: TObject;
  ARequestResult: TTMSMCPCloudBaseRequestResult);
begin
  lblStatus.Text := 'Failed';

  { OnRequestError covers three different failures: the HTTP call itself,
    a response the service rejected, and a successful response that carried
    no image. ResponseCode tells them apart. }
  memoLog.Lines.Add(Format('%s failed with HTTP %d',
    [ARequestResult.Name, ARequestResult.ResponseCode]));
  memoLog.Lines.Add(ARequestResult.ResultString);
end;

The request events inherited from TTMSMCPCloudBaseOnRequestStarted, OnRequestProgress, OnRequestComplete — fire for the generation request and for every poll it triggers. On a polling service that means several started events for one image, so drive a progress indicator from them but do not count generations with them.

One failure has no event at all: an empty APIKey makes Execute, RemoveBackground, and ReplaceBackground raise Please fill in the API Key. at the call site. Check the key before the call, or handle the exception there.

Combining reference images, a background replacement, and the saved result

A realistic pipeline chains the calls: compose from several references, restage the composition against a new background, and save each stage. Because every stage ends in the same event, the chaining happens inside OnImageGenerated:

procedure TForm1.RunCampaignShot(const ASubjectFiles: TArray<string>;
  const ABackgroundFile: string);
var
  FileName: string;
begin
  FStage := csCompose;
  FBackgroundFile := ABackgroundFile;

  { 1. Several reference images through the Images collection. }
  ImageAI.Images.Clear;
  for FileName in ASubjectFiles do
    ImageAI.Images.Add.Stream.LoadFromFile(FileName);

  { 2. One prompt over all of them - this is still Execute(APrompt), it is
       the populated collection that makes it a multi-reference request. }
  ImageAI.Execute('Arrange these products on a single neutral surface, ' +
    'evenly lit, seen slightly from above.');
end;

procedure TForm1.ImageAIImageGenerated(Sender: TObject;
  ARequestResult: TTMSMCPCloudBaseRequestResult; ABase64Image: string);
var
  Stream, Background: TMemoryStream;
begin
  { 3. Save whatever stage just completed. }
  Stream := TMemoryStream.Create;
  try
    TTMSMCPUtils.LoadStreamFromBase64(ABase64Image, Stream);
    Stream.SaveToFile(TPath.Combine(FOutputFolder,
      Format('stage-%d.png', [Ord(FStage)])));

    if FStage = csCompose then
    begin
      { 4. Feed the composed image straight into a background replacement.
           Clear Images first: the collection is not consumed by Execute,
           and the reference images are no longer wanted. }
      ImageAI.Images.Clear;
      FStage := csRestage;

      Background := TMemoryStream.Create;
      try
        Background.LoadFromFile(FBackgroundFile);
        Stream.Position := 0;
        ImageAI.ReplaceBackground(Stream, Background);
      finally
        Background.Free;
      end;
    end
    else
    begin
      Stream.Position := 0;
      imgResult.Bitmap.LoadFromStream(Stream);
    end;
  finally
    Stream.Free;
  end;
end;

The state field is what makes this readable. Both stages arrive through one handler, so something has to say which one just finished — and the same field tells the handler whether to chain again or stop.

Common mistakes

  • Reading the result from the Execute call. It returns as soon as the request is dispatched. Everything happens in OnImageGenerated or OnRequestError.
  • Leaving Images populated. The collection is not cleared by Execute, so a leftover reference turns the next text-to-image request into an edit. Clear it before a fresh generation.
  • Re-enabling the UI in only one result path. Both events are terminal. Handle the error path too, or the control stays disabled after a failure.
  • Not rewinding the decoded stream. After SaveToFile the position is at the end; a picture control then loads nothing. Set Position := 0.
  • Passing the background image first to ReplaceBackground. Subject first, background second. The wrong order is not rejected.
  • Expecting a dedicated background endpoint everywhere. Only some services have one; elsewhere the call is a written instruction. Use Execute with your own prompt when the shortcut is not precise enough.
  • Calling Execute with an empty APIKey. It raises at the call site rather than reporting through OnRequestError.
  • Counting OnRequestStarted as one per image. A polling service fires it for every poll as well.

See also