summaryrefslogtreecommitdiff
path: root/internal/image
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-04-21 23:12:52 +0300
committerPaul Buetow <paul@buetow.org>2026-04-21 23:12:52 +0300
commit3e99f5a75e14455aa75ca3a1dc7d08bd3e6f55fd (patch)
tree568bb781c0d71bdea58f5dc7192e6c5edec94c27 /internal/image
parentd15846a2784d15c5043833874b6539b727555cb0 (diff)
u7: wire documented config knobs into runtime
Diffstat (limited to 'internal/image')
-rw-r--r--internal/image/gemini.go28
-rw-r--r--internal/image/gemini_attribution.go6
-rw-r--r--internal/image/gemini_test.go28
-rw-r--r--internal/image/registry.go8
-rw-r--r--internal/image/types_test.go1
5 files changed, 63 insertions, 8 deletions
diff --git a/internal/image/gemini.go b/internal/image/gemini.go
index 05a2e0e..bd418d5 100644
--- a/internal/image/gemini.go
+++ b/internal/image/gemini.go
@@ -28,9 +28,10 @@ const (
// GeminiConfig holds the settings needed to build a Gemini-backed image provider.
type GeminiConfig struct {
- APIKey string
- Model string
- TextModel string
+ APIKey string
+ Model string
+ TextModel string
+ AspectRatio string
}
// GeminiProvider implements ImageProvider for Google Gemini image generation.
@@ -104,6 +105,9 @@ func (c *GeminiProvider) Search(ctx context.Context, opts *SearchOptions) ([]Sea
}
aspectRatio := geminiAspectRatio
+ if c.config != nil && strings.TrimSpace(c.config.AspectRatio) != "" {
+ aspectRatio = strings.TrimSpace(c.config.AspectRatio)
+ }
if opts.AspectRatio != "" {
aspectRatio = opts.AspectRatio
}
@@ -164,13 +168,26 @@ func (c *GeminiProvider) IsAvailable() error {
// GenerateImage renders the first generated image to outputFile.
func (c *GeminiProvider) GenerateImage(ctx context.Context, prompt, outputFile string) error {
- return c.GenerateImageWithReferences(ctx, prompt, outputFile, nil)
+ return c.GenerateImageWithAspectRatio(ctx, prompt, outputFile, "")
+}
+
+// GenerateImageWithAspectRatio renders the first generated image to outputFile
+// using the configured aspect ratio or the supplied override.
+func (c *GeminiProvider) GenerateImageWithAspectRatio(ctx context.Context, prompt, outputFile string, aspectRatio string) error {
+ return c.GenerateImageWithReferencesAndAspectRatio(ctx, prompt, outputFile, nil, aspectRatio)
}
// GenerateImageWithReferences renders the first generated image to outputFile,
// optionally conditioning the model on prior page images so comic pages stay
// visually consistent across the full PDF.
func (c *GeminiProvider) GenerateImageWithReferences(ctx context.Context, prompt, outputFile string, refs [][]byte) error {
+ return c.GenerateImageWithReferencesAndAspectRatio(ctx, prompt, outputFile, refs, "")
+}
+
+// GenerateImageWithReferencesAndAspectRatio renders the first generated image
+// to outputFile, optionally conditioning on reference images and overriding the
+// aspect ratio.
+func (c *GeminiProvider) GenerateImageWithReferencesAndAspectRatio(ctx context.Context, prompt, outputFile string, refs [][]byte, aspectRatio string) error {
if c == nil {
return fmt.Errorf("image provider is nil")
}
@@ -187,6 +204,9 @@ func (c *GeminiProvider) GenerateImageWithReferences(ctx context.Context, prompt
if len(refs) > 0 {
opts.ReferenceImages = refs
}
+ if strings.TrimSpace(aspectRatio) != "" {
+ opts.AspectRatio = strings.TrimSpace(aspectRatio)
+ }
results, err := c.Search(ctx, opts)
if err != nil {
return err
diff --git a/internal/image/gemini_attribution.go b/internal/image/gemini_attribution.go
index 2aefeac..15d1bb1 100644
--- a/internal/image/gemini_attribution.go
+++ b/internal/image/gemini_attribution.go
@@ -22,7 +22,11 @@ func (c *GeminiProvider) buildAttribution(result *SearchResult, prompt string) s
attribution.WriteString("Image generated by Google Gemini Nano Banana\n\n")
fmt.Fprintf(&attribution, "Model: %s\n", c.modelName())
fmt.Fprintf(&attribution, "Text model: %s\n", c.textModelName())
- fmt.Fprintf(&attribution, "Aspect ratio: %s\n", geminiAspectRatio)
+ aspectRatio := geminiAspectRatio
+ if c != nil && c.config != nil && strings.TrimSpace(c.config.AspectRatio) != "" {
+ aspectRatio = strings.TrimSpace(c.config.AspectRatio)
+ }
+ fmt.Fprintf(&attribution, "Aspect ratio: %s\n", aspectRatio)
fmt.Fprintf(&attribution, "Size: %dx%d\n", result.Width, result.Height)
if result.Description != "" {
fmt.Fprintf(&attribution, "Result: %s\n", result.Description)
diff --git a/internal/image/gemini_test.go b/internal/image/gemini_test.go
index 91e0ce4..b605f7a 100644
--- a/internal/image/gemini_test.go
+++ b/internal/image/gemini_test.go
@@ -115,6 +115,34 @@ func TestGeminiProvider_Search_CustomPromptSkipsTextGeneration(t *testing.T) {
}
}
+func TestGeminiProvider_UsesConfiguredAspectRatio(t *testing.T) {
+ originalText := geminiGenerateText
+ originalImage := geminiGenerateImage
+ t.Cleanup(func() {
+ geminiGenerateText = originalText
+ geminiGenerateImage = originalImage
+ })
+
+ geminiGenerateText = func(context.Context, *GeminiProvider, string, string, string, float32, int32) (string, error) {
+ t.Fatal("unexpected text generation for configured aspect ratio")
+ return "", nil
+ }
+
+ var gotAspectRatio string
+ geminiGenerateImage = func(_ context.Context, _ *GeminiProvider, prompt, aspectRatio string) ([]byte, string, error) {
+ gotAspectRatio = aspectRatio
+ return mustJPEGBytes(t), "image/jpeg", nil
+ }
+
+ client := NewGeminiProvider(&GeminiConfig{APIKey: "test-key", AspectRatio: "1:1"})
+ if err := client.GenerateImage(context.Background(), "Art style: comic.", t.TempDir()+"/out.png"); err != nil {
+ t.Fatalf("GenerateImage() unexpected error: %v", err)
+ }
+ if got, want := gotAspectRatio, "1:1"; got != want {
+ t.Fatalf("aspect ratio = %q, want %q", got, want)
+ }
+}
+
func TestGeminiProvider_Search_CustomPromptWithoutTranslationKeepsDescriptionSimple(t *testing.T) {
originalText := geminiGenerateText
originalImage := geminiGenerateImage
diff --git a/internal/image/registry.go b/internal/image/registry.go
index 2d446e8..abf7917 100644
--- a/internal/image/registry.go
+++ b/internal/image/registry.go
@@ -20,6 +20,7 @@ type GeminiRegistryConfig interface {
GoogleAPIKey() string
ImageModel() string
ImageTextModel() string
+ ComicAspectRatio() string
}
// Registry resolves provider names to factories.
@@ -96,9 +97,10 @@ func DefaultRegistry() *Registry[GeminiRegistryConfig] {
registry := NewRegistry[GeminiRegistryConfig]()
registry.Register(Gemini, func(cfg GeminiRegistryConfig) (ImageProvider, error) {
geminiProvider := NewGeminiProvider(&GeminiConfig{
- APIKey: cfg.GoogleAPIKey(),
- Model: cfg.ImageModel(),
- TextModel: cfg.ImageTextModel(),
+ APIKey: cfg.GoogleAPIKey(),
+ Model: cfg.ImageModel(),
+ TextModel: cfg.ImageTextModel(),
+ AspectRatio: cfg.ComicAspectRatio(),
})
if err := geminiProvider.IsAvailable(); err != nil {
return nil, err
diff --git a/internal/image/types_test.go b/internal/image/types_test.go
index 5916389..3b83a50 100644
--- a/internal/image/types_test.go
+++ b/internal/image/types_test.go
@@ -162,6 +162,7 @@ func (f fakeConfig) ImageProviderName() string { return f.name }
func (f fakeConfig) GoogleAPIKey() string { return f.apiKey }
func (f fakeConfig) ImageModel() string { return f.model }
func (f fakeConfig) ImageTextModel() string { return f.textModel }
+func (f fakeConfig) ComicAspectRatio() string { return "" }
type customConfig struct {
name string