はじめに
こんにちは!
先日、以下の記事を書きました。
Swift & SwiftUIでタロットカードアプリを作る
[Swift] ホラー画像生成アプリ
今回はこれらをマージして、タロットカードの画像を自動生成してみました。
簡易版ですが、動きは良さそうなので共有します!
要件
- ボタンをタップすると、タロットカードをランダムに選択する
- タロットカードの名前と意味に沿って画像を生成する
- タロットカードの名前、画像、意味を表示する
準備
プロジェクトの作成方法は、Swift & SwiftUIでタロットカードアプリを作る を参照してください。
User InterfaceはSwiftUI
を選択しました。
実装
ContentViewを修正
ContentView.swift
//
// ContentView.swift
// TarotApp
//
import SwiftUI
struct ContentView: View {
@State private var selectedCard: TarotCard?
var body: some View {
NavigationView {
VStack(spacing: 20) {
Text("TarotApp")
.font(.largeTitle)
.fontWeight(.bold)
.padding()
.frame(maxWidth: .infinity) // タイトルを中央寄せにする
.background(Color.gray.opacity(0.2))
.cornerRadius(10)
if let card = selectedCard {
CardView(card: card)
.id(card.id)
Button("もう一度引く") {
// ランダムにカードを選択
selectedCard = tarotCards.randomElement()
}
.padding()
.background(Color.blue)
.foregroundColor(.white)
.cornerRadius(10)
} else {
Button("カードを引く") {
// ランダムにカードを選択
selectedCard = tarotCards.randomElement()
}
.padding()
.background(Color.blue)
.foregroundColor(.white)
.cornerRadius(10)
}
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
CardViewも修正
CardView.swift
// CardView.swift
// TarotApp
//
import SwiftUI
struct CardView: View {
let card: TarotCard
// OpenAI APIキーを設定
private let apiKey = "sk-proj-xxx"
// 画状態変数を追加
@State private var outputImage: UIImage?
@State private var isLoading = false
@State private var showAlert = false
var body: some View {
VStack(spacing: 10) {
Text(card.name)
.font(.largeTitle)
.fontWeight(.bold)
.padding()
.background(Color.gray.opacity(0.2))
.cornerRadius(10)
Text(card.description)
.font(.headline)
.multilineTextAlignment(.center)
.padding()
// 画像を表示
if let outputImage = outputImage {
Image(uiImage: outputImage)
.resizable()
.scaledToFit()
.cornerRadius(10)
.padding()
} else {
// 画像がない場合の表示
ProgressView()
.opacity(isLoading ? 1.0 : 0.0)
.padding()
}
Spacer()
Text("意味")
.font(.title2)
.fontWeight(.bold)
.padding()
Text(card.meaning)
.font(.body)
.padding()
}
.padding()
.background(Color(UIColor.systemGray6))
.cornerRadius(20)
.shadow(radius: 5)
.onAppear {
// カード表示時に画像を生成
isLoading = true
generateImage(from: card.name, from: card.description)
}
// アラート表示
.alert("エラー", isPresented: $showAlert) {
Button("OK", role: .cancel) { }
} message: {
Text("画像の生成に失敗しました。")
}
}
func generateImage(from name: String, from description: String) {
let url = URL(string: "https://api.openai.com/v1/images/generations")!
// ホラーテイストを必ず入れるためのプロンプト調整
let horrorPrompt = "An eerie and unsettling scene with '\(name), \(description)', in the style of a horror and cyber-punk, dark and gruesome, high quality, digital art."
let parameters: [String: Any] = [
"model": "dall-e-3", // dall-e-3に指定
"prompt": horrorPrompt,
"n": 1,
"size": "1024x1024" // 画像サイズ
]
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.addValue("Bearer \(apiKey)", forHTTPHeaderField: "Authorization")
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpBody = try? JSONSerialization.data(withJSONObject: parameters)
URLSession.shared.dataTask(with: request) { data, response, error in
// エラー処理
if let error = error {
print("エラー: \(error)")
DispatchQueue.main.async {
self.showAlert = true
self.isLoading = false // エラー時にローディングを停止
}
return
}
guard let data = data else {
print("データがありません")
DispatchQueue.main.async {
self.showAlert = true
self.isLoading = false // エラー時にローディングを停止
}
return
}
do {
if let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any],
let dataArray = json["data"] as? [[String: Any]],
let firstImageData = dataArray.first,
let imageUrlString = firstImageData["url"] as? String,
let imageUrl = URL(string: imageUrlString) {
// 画像の取得
if let imageData = try? Data(contentsOf: imageUrl), let image = UIImage(data: imageData) {
DispatchQueue.main.async {
self.outputImage = image
self.isLoading = false
}
} else {
print("画像の取得またはデコードエラー")
DispatchQueue.main.async {
self.showAlert = true
self.isLoading = false // エラー時にローディングを停止
}
}
} else {
print("画像URLの解析エラー")
DispatchQueue.main.async {
self.showAlert = true
self.isLoading = false // エラー時にローディングを停止
}
}
} catch {
print("JSON解析エラー: \(error)")
DispatchQueue.main.async {
self.showAlert = true
self.isLoading = false // エラー時にローディングを停止
}
}
}.resume()
}
}
TarotCardsはそのまま使います。
TarotCards.swift
//
// TarotCards.swift
// TarotApp
//
import SwiftUI
struct TarotCard: Identifiable {
let id = UUID()
let name: String
let description: String
let meaning: String
}
let tarotCards: [TarotCard] = [
TarotCard(name: "The Fool",
description: "始まり、無邪気さ、新しい旅立ち",
meaning: "新しい章の始まり、自由な精神、冒険心"),
TarotCard(name: "The Magician",
description: "意志力、創造力、潜在能力",
meaning: "目標達成、能力発揮、潜在能力の開花"),
TarotCard(name: "The High Priestess",
description: "直感、神秘、秘密",
meaning: "内なる知恵、直感力、秘密の解明"),
TarotCard(name: "The Empress",
description: "豊穣、愛情、自然",
meaning: "豊かさ、愛情、創造性"),
TarotCard(name: "The Emperor",
description: "権力、安定、秩序",
meaning: "安定、統率力、権威"),
TarotCard(name: "The Hierophant",
description: "伝統、信仰、精神的指導",
meaning: "伝統、精神的な指導、社会規範"),
TarotCard(name: "The Lovers",
description: "愛、調和、選択",
meaning: "愛情、調和、パートナーシップ"),
TarotCard(name: "The Chariot",
description: "意志、勝利、克服",
meaning: "意志力、目標達成、克服"),
TarotCard(name: "Strength",
description: "強さ、勇気、忍耐",
meaning: "内なる強さ、克服、忍耐"),
TarotCard(name: "The Hermit",
description: "内省、孤独、自己探求",
meaning: "内観、自己成長、探求"),
TarotCard(name: "Wheel of Fortune",
description: "運命、変化、サイクル",
meaning: "運命、変化、サイクル"),
TarotCard(name: "Justice",
description: "公正、均衡、法則",
meaning: "公正、バランス、調和"),
TarotCard(name: "The Hanged Man",
description: "犠牲、視点を変える、新しい見方",
meaning: "新しい視点、見直し、変化"),
TarotCard(name: "Death",
description: "終わり、変容、再生",
meaning: "終わりと始まり、変容、再生"),
TarotCard(name: "Temperance",
description: "調和、バランス、忍耐",
meaning: "バランス、調和、忍耐"),
TarotCard(name: "The Devil",
description: "誘惑、執着、物質主義",
meaning: "誘惑、執着、依存"),
TarotCard(name: "The Tower",
description: "破壊、変化、混乱",
meaning: "破壊、変革、混乱"),
TarotCard(name: "The Star",
description: "希望、再生、癒し",
meaning: "希望、再生、癒し"),
TarotCard(name: "The Moon",
description: "幻想、直感、隠された真実",
meaning: "幻想、直感、隠された真実"),
TarotCard(name: "The Sun",
description: "喜び、成功、幸福",
meaning: "喜び、成功、幸福"),
TarotCard(name: "Judgement",
description: "再生、自己評価、新しい始まり",
meaning: "再生、自己評価、新しい始まり"),
TarotCard(name: "The World",
description: "完成、達成、統合",
meaning: "完成、達成、統合"),
TarotCard(name: "The Major Arcana",
description: "運命のサイクル、人生の旅路",
meaning: "人生の重要な課題、試練"),
TarotCard(name: "Ace of Wands",
description: "新しい始まり、情熱、創造性",
meaning: "新しいプロジェクト、情熱、創造性"),
TarotCard(name: "Two of Wands",
description: "計画、展望、決断",
meaning: "計画、展望、決断"),
TarotCard(name: "Three of Wands",
description: "拡大、成長、冒険",
meaning: "拡大、成長、冒険"),
TarotCard(name: "Four of Wands",
description: "安定、祝賀、達成",
meaning: "安定、祝賀、達成"),
TarotCard(name: "Five of Wands",
description: "競争、対立、混乱",
meaning: "競争、対立、混乱"),
TarotCard(name: "Six of Wands",
description: "成功、勝利、栄光",
meaning: "成功、勝利、栄光"),
TarotCard(name: "Seven of Wands",
description: "防衛、抵抗、忍耐",
meaning: "防衛、抵抗、忍耐"),
TarotCard(name: "Eight of Wands",
description: "動き、変化、スピード",
meaning: "動き、変化、スピード"),
TarotCard(name: "Nine of Wands",
description: "忍耐、決意、防衛",
meaning: "忍耐、決意、防衛"),
TarotCard(name: "Ten of Wands",
description: "負担、責任、完成",
meaning: "負担、責任、完成"),
TarotCard(name: "Page of Wands",
description: "新しいアイデア、熱意、探検",
meaning: "新しいアイデア、熱意、探検"),
TarotCard(name: "Knight of Wands",
description: "行動力、情熱、冒険心",
meaning: "行動力、情熱、冒険心"),
TarotCard(name: "Queen of Wands",
description: "独立心、自信、創造性",
meaning: "独立心、自信、創造性"),
TarotCard(name: "King of Wands",
description: "リーダーシップ、成功、野心",
meaning: "リーダーシップ、成功、野心"),
TarotCard(name: "Ace of Cups",
description: "愛情、感情、創造性",
meaning: "愛情、感情、創造性"),
TarotCard(name: "Two of Cups",
description: "調和、愛情、パートナーシップ",
meaning: "調和、愛情、パートナーシップ"),
TarotCard(name: "Three of Cups",
description: "祝賀、友情、喜び",
meaning: "祝賀、友情、喜び"),
TarotCard(name: "Four of Cups",
description: "内省、満足、孤独",
meaning: "内省、満足、孤独"),
TarotCard(name: "Five of Cups",
description: "悲しみ、喪失、失望",
meaning: "悲しみ、喪失、失望"),
TarotCard(name: "Six of Cups",
description: "過去、思い出、ノスタルジア",
meaning: "過去、思い出、ノスタルジア"),
TarotCard(name: "Seven of Cups",
description: "幻想、誘惑、選択",
meaning: "幻想、誘惑、選択"),
TarotCard(name: "Eight of Cups",
description: "離脱、失望、新しい出発",
meaning: "離脱、失望、新しい出発"),
TarotCard(name: "Nine of Cups",
description: "満足、幸福、願いの成就",
meaning: "満足、幸福、願いの成就"),
TarotCard(name: "Ten of Cups",
description: "完璧な幸福、家庭、愛",
meaning: "完璧な幸福、家庭、愛"),
TarotCard(name: "Page of Cups",
description: "新しい愛、感情、創造性",
meaning: "新しい愛、感情、創造性"),
TarotCard(name: "Knight of Cups",
description: "ロマンチック、夢、理想",
meaning: "ロマンチック、夢、理想"),
TarotCard(name: "Queen of Cups",
description: "共感、慈悲、直感",
meaning: "共感、慈悲、直感"),
TarotCard(name: "King of Cups",
description: "感情の支配、感情の安定、調和",
meaning: "感情の支配、感情の安定、調和"),
TarotCard(name: "Ace of Swords",
description: "新しいアイデア、決断、突破",
meaning: "新しいアイデア、決断、突破"),
TarotCard(name: "Two of Swords",
description: "停滞、決断の遅延、葛藤",
meaning: "停滞、決断の遅延、葛藤"),
TarotCard(name: "Three of Swords",
description: "悲しみ、失恋、苦痛",
meaning: "悲しみ、失恋、苦痛"),
TarotCard(name: "Four of Swords",
description: "休息、回復、静寂",
meaning: "休息、回復、静寂"),
TarotCard(name: "Five of Swords",
description: "敗北、裏切り、争い",
meaning: "敗北、裏切り、争い"),
TarotCard(name: "Six of Swords",
description: "移転、旅立ち、癒し",
meaning: "移転、旅立ち、癒し"),
TarotCard(name: "Seven of Swords",
description: "裏切り、詐欺、隠蔽",
meaning: "裏切り、詐欺、隠蔽"),
TarotCard(name: "Eight of Swords",
description: "制限、束縛、囚われ",
meaning: "制限、束縛、囚われ"),
TarotCard(name: "Nine of Swords",
description: "不安、恐怖、悪夢",
meaning: "不安、恐怖、悪夢"),
TarotCard(name: "Ten of Swords",
description: "破壊、敗北、絶望",
meaning: "破壊、敗北、絶望"),
TarotCard(name: "Page of Swords",
description: "知性、メッセージ、情報",
meaning: "知性、メッセージ、情報"),
TarotCard(name: "Knight of Swords",
description: "迅速な行動、攻撃性、決断力",
meaning: "迅速な行動、攻撃性、決断力"),
TarotCard(name: "Queen of Swords",
description: "独立心、知性、冷静",
meaning: "独立心、知性、冷静"),
TarotCard(name: "King of Swords",
description: "権威、支配、正義",
meaning: "権威、支配、正義"),
TarotCard(name: "Ace of Pentacles",
description: "豊かさ、財産、新しい始まり",
meaning: "豊かさ、財産、新しい始まり"),
TarotCard(name: "Two of Pentacles",
description: "バランス、多忙、調整",
meaning: "バランス、多忙、調整"),
TarotCard(name: "Three of Pentacles",
description: "チームワーク、職人技、協力",
meaning: "チームワーク、職人技、協力"),
TarotCard(name: "Four of Pentacles",
description: "安全、安定、所有",
meaning: "安全、安定、所有"),
TarotCard(name: "Five of Pentacles",
description: "貧困、失業、孤独",
meaning: "貧困、失業、孤独"),
TarotCard(name: "Six of Pentacles",
description: "慈善、寛大、共有",
meaning: "慈善、寛大、共有"),
TarotCard(name: "Seven of Pentacles",
description: "努力、辛抱、成果",
meaning: "努力、辛抱、成果"),
TarotCard(name: "Eight of Pentacles",
description: "技能、熟練、努力",
meaning: "技能、熟練、努力"),
TarotCard(name: "Nine of Pentacles",
description: "成功、豊かさ、満足",
meaning: "成功、豊かさ、満足"),
TarotCard(name: "Ten of Pentacles",
description: "遺産、財産、家族",
meaning: "遺産、財産、家族"),
TarotCard(name: "Page of Pentacles",
description: "新しい機会、学習、潜在能力",
meaning: "新しい機会、学習、潜在能力"),
TarotCard(name: "Knight of Pentacles",
description: "勤勉、責任、安定",
meaning: "勤勉、責任、安定"),
TarotCard(name: "Queen of Pentacles",
description: "豊穣、実用性、安定",
meaning: "豊穣、実用性、安定"),
TarotCard(name: "King of Pentacles",
description: "富、成功、物質的な豊かさ",
meaning: "富、成功、物質的な豊かさ")
]
動作確認
起動直後の画面。4回引いてみます。
1回目
Ace of Wands
2回目
Knight of Swords
3回目
The Emperor
4回目
The Fool
いい感じですね!
あとは画像サイズやデザインを工夫すれば、良さそうです。
最後に
今回は、タロットカードと画像生成を組み合わせてみました。
プロンプトによって様々なテイストの画像を生成できるので、みなさんもチャレンジしてみてください😀