필자는 아래와 같이 주어진 스크립트에 대해 이미지를 생성하는 dall-e-3를 활용해보고자 한다. 아래 사진의 경우 '컴퓨터 배경화면으로 사용할 그림 그려줘'라는 요청을 보냈다.

dependencies {
...
implementation 'com.squareup.okhttp3:okhttp:4.10.0'
}
build.gradle 수정 후 오른쪽 상단 위에 Sync now 클릭
dall-e와 응답을 주고받기 위해서 통신을 해야 하므로, 인터넷 권한을 부여해줘야 한다.
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET" />
...
</manifest>
응답을 보내기 위한 icon을 추가하였다.
왼쪽 탭의 Resource Manager > + 버튼 > Import Drawables 누른 후, 다운받은 이미지 추가 > Next 버튼 > Import 버튼 차례로 클릭
Resource Manager에 이미지(icon_send) 추가되었는지 확인

GPT API 공식문서에 따르면, dall-e-3의 request format은 아래와 같다.
curl https://api.openai.com/v1/images/generations \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-d '{
"model": "dall-e-3",
"prompt": "A cute baby sea otter",
"n": 1,
"size": "1024x1024"
}'
위의 형식을 살펴보면, Content-Type과 Authorization을 지정해주고, model및 prompt, n, size로 구성된 json을 가지고 통신한다는 것을 알 수 있다. 이 때, Authorization의 $OPENAI_API_KEY가 사용자의 API Key에 해당한다.
또한, prompt가 이미지를 생성할 스크립트, n이 이미지의 개수(dall-e-3는 반드시 n이 1이어야 한다), size는 이미지의 크기를 의미한다.
dall-e-3의 response format은 아래와 같다.
{
"created": 1589478378,
"data": [
{
"url": "https://..."
},
{
"url": "https://..."
}
]
}
dall-e-3는 생성된 이미지를 url 형태로 전달하기 때문에, 전달받은 json으로부터 url을 추출하고, 이를 ImageView에 띄우면 성공적으로 앱의 사용자에게 이미지가 보여지게 된다.
TextView와 EditText, ImageView를 적절히 배치하여 채팅을 주고받을 수 있는 xml 파일을 생성한다.
EditText의 속성으로 inputType="textMultiLine"과 maxLines="5"를 추가하여 실제 카톡처럼 여러줄을 입력하면 EditText의 높이가 높아지도록 하였다.
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<EditText
android:id="@+id/requestEditText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:layout_marginBottom="16dp"
android:background="@drawable/round_button"
android:ems="10"
android:hint="write here"
android:inputType="textMultiLine"
android:maxLines="5"
android:padding="10dp"
android:privateImeOptions="defaultInputmode=korean"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/sendBtn"
app:layout_constraintStart_toStartOf="parent" />
<ImageView
android:id="@+id/sendBtn"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="16dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:srcCompat="@drawable/icon_send" />
<ImageView
android:id="@+id/responseImg"
android:layout_width="300dp"
android:layout_height="300dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:srcCompat="@tools:sample/avatars" />
<TextView
android:id="@+id/showText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="스크립트를 입력해주세요"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.498"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.755" />
</androidx.constraintlayout.widget.ConstraintLayout>

chatGPT를 사용하기 위해선 API key를 발급받아야 한다.
GPT-API key 사이트로 접속하여 Create new secret key 버튼을 클릭하여 API key를 발급받는다. 발급받은 key는 다시 확인할 수 없으니 메모장에 반드시 복사-붙여넣기 해야한다.
또한, key를 발급받아도 결제를 하지 않으면 GPT와 응답을 주고받을 수 없으므로 GPT billing 사이트로 접속하여 add payment details 버튼을 클릭하여 카드를 등록하고, 기본요금 $5.5를 결제한다.
해당 과정에서 구현해야 할 것은 크게 아래와 같이 구분할 수 있다.
1. onCreate함수
2. showText함수
3. setImg함수
4. callAPI함수
5. showImage함수
먼저 onCreate함수에서는 앞서 설정한 send 버튼을 클릭 시 editText의 내용을 지움과 동시에 dall-e-3에게 응답을 보내는 코드를 구현한다. 아래의 코드에서 callAPI함수는 나중에 구현한다.
또한, client는 http 통신을 위해 반드시 구현되어야 한다.
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
editText = findViewById(R.id.requestEditText);
sendBtn = findViewById(R.id.sendBtn);
responseImg = findViewById(R.id.responseImg);
textView = findViewById(R.id.showText);
sendBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
System.out.println(editText.getText());
String question = editText.getText().toString().trim();
editText.setText("");
callAPI(question);
}
});
client = new OkHttpClient().newBuilder()
.connectTimeout(60, TimeUnit.SECONDS)
.writeTimeout(120, TimeUnit.SECONDS)
.readTimeout(60, TimeUnit.SECONDS)
.build();
}
다음으로 showText함수를 구현한다. 해당 함수는 dall-e-3와 통신하는 중에 띄울 안내 메시지를 위해 구현해야 한다. 예를 들면, dall-e-3로부터 전달받은 url을 다운받을 때 "이미지 다운로드 중..."과 같은 안내 문구를 띄운다. 이 함수는 runOnUiThread함수를 이용하여 다른 thread에서도 view에 접근 및 수정이 가능하다(showText함수 없이 바로 추가할 경우 에러 발생).
void showText(String string) {
runOnUiThread(new Runnable() {
@Override
public void run() {
textView.setText(string);
}
});
}
setImg함수는 dall-e-3으로부터 다운받은 Bitmap 이미지를 ImageView에 띄울 때 사용하는 함수이다. 이 함수도 showText함수와 마찬가지로 runOnUiThread함수를 이용하여 다른 thread에서도 view에 접근 및 수정이 가능하게 했다.
void setImg(Bitmap bitmap) {
runOnUiThread(new Runnable() {
@Override
public void run() {
responseImg.setImageBitmap(bitmap);
}
});
}
callAPI함수는 dall-e-3에게 응답을 보내고, 받아오는 역할을 한다. 위에서 살펴봤던 request format에 맞추어 JSONObject를 구성하고, 요청을 보내면 된다. client의 Callback 함수에서 jsonArray.getJSONObject(0).getString("url")부분이 dall-e-3의 response format에 맞추어 답변 내용을 가져오는 코드이다.
void callAPI(String question){
JSONObject object = new JSONObject();
try {
object.put("model", "dall-e-3");
object.put("prompt", question);
object.put("size", "1024x1024");
} catch (JSONException e){
e.printStackTrace();
}
RequestBody body = RequestBody.create(object.toString(), JSON);
Request request = new Request.Builder()
.url("https://api.openai.com/v1/images/generations")
.header("Authorization", "Bearer "+MY_SECRET_KEY)
.post(body)
.build();
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(@NonNull Call call, @NonNull IOException e) {
showText("이미지 생성 실패\n" + e.getMessage());
}
@Override
public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {
if (response.isSuccessful()) {
JSONObject jsonObject = null;
try {
jsonObject = new JSONObject(response.body().string());
JSONArray jsonArray = jsonObject.getJSONArray("data");
String result = jsonArray.getJSONObject(0).getString("url");
showText("이미지 다운로드 중...");
showImage(result);
} catch (JSONException e) {
e.printStackTrace();
}
} else {
showText("이미지 생성 실패\n" + response.body().string());
}
}
});
}
마지막으로 showImage함수는 dall-e-3로부터 전달받은 url을 바탕으로 이미지를 다운받아 ImageView에 띄우는 역할을 하는 함수이다. 이미지를 다운받을 수 있는 Thread를 하나 생성하고, Thread가 종료될 때까지 기다린다(while문을 통한 busy waiting). 이후 Thread가 종료된다면 그 때 Bitmap을 ImageView에 띄운다.
public void showImage(String urlResponse){
showText("이미지 다운로드 중...");
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
try {
URL url = new URL(urlResponse);
bitmapOutputImage = BitmapFactory.decodeStream(url.openStream());
} catch (MalformedURLException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
});
thread.start();
while (thread.isAlive()) { }
Bitmap bitmapFinalImage = Bitmap.createScaledBitmap(bitmapOutputImage,
responseImg.getWidth(), responseImg.getHeight(), true);
setImg(bitmapFinalImage);
showText("이미지 다운로드 완료");
}
전체 코드는 아래와 같다.
package com.example.chatgpt;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.RecyclerView;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.view.View;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.TextView;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.concurrent.TimeUnit;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
public class MainActivity extends AppCompatActivity {
EditText editText;
ImageView sendBtn, responseImg;
TextView textView;
Bitmap bitmapOutputImage;
public static final MediaType JSON = MediaType.get("application/json; charset=utf-8");
private static final String MY_SECRET_KEY = "your-api-key";
OkHttpClient client;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
editText = findViewById(R.id.requestEditText);
sendBtn = findViewById(R.id.sendBtn);
responseImg = findViewById(R.id.responseImg);
textView = findViewById(R.id.showText);
sendBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
System.out.println(editText.getText());
String question = editText.getText().toString().trim();
editText.setText("");
callAPI(question);
}
});
client = new OkHttpClient().newBuilder()
.connectTimeout(60, TimeUnit.SECONDS)
.writeTimeout(120, TimeUnit.SECONDS)
.readTimeout(60, TimeUnit.SECONDS)
.build();
}
void showText(String string) {
runOnUiThread(new Runnable() {
@Override
public void run() {
textView.setText(string);
}
});
}
void setImg(Bitmap bitmap) {
runOnUiThread(new Runnable() {
@Override
public void run() {
responseImg.setImageBitmap(bitmap);
}
});
}
void callAPI(String question){
JSONObject object = new JSONObject();
try {
object.put("model", "dall-e-3");
object.put("prompt", question);
object.put("size", "1024x1024");
} catch (JSONException e){
e.printStackTrace();
}
RequestBody body = RequestBody.create(object.toString(), JSON);
Request request = new Request.Builder()
.url("https://api.openai.com/v1/images/generations")
.header("Authorization", "Bearer "+MY_SECRET_KEY)
.post(body)
.build();
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(@NonNull Call call, @NonNull IOException e) {
showText("이미지 생성 실패\n" + e.getMessage());
}
@Override
public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {
if (response.isSuccessful()) {
JSONObject jsonObject = null;
try {
jsonObject = new JSONObject(response.body().string());
JSONArray jsonArray = jsonObject.getJSONArray("data");
String result = jsonArray.getJSONObject(0).getString("url");
showText("이미지 다운로드 중...");
showImage(result);
} catch (JSONException e) {
e.printStackTrace();
}
} else {
showText("이미지 생성 실패\n" + response.body().string());
}
}
});
}
public void showImage(String urlResponse){
showText("이미지 다운로드 중...");
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
try {
URL url = new URL(urlResponse);
bitmapOutputImage = BitmapFactory.decodeStream(url.openStream());
} catch (MalformedURLException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
});
thread.start();
while (thread.isAlive()) { }
Bitmap bitmapFinalImage = Bitmap.createScaledBitmap(bitmapOutputImage,
responseImg.getWidth(), responseImg.getHeight(), true);
setImg(bitmapFinalImage);
showText("이미지 다운로드 완료");
}
}
앱을 실행시키면 아래와 같이 작동하게 된다. 아래 사진의 경우 '컴퓨터 배경화면으로 사용할 그림 그려줘'라는 요청을 보냈다.
