diff options
| author | Paul Buetow <paul@buetow.org> | 2026-04-21 23:12:52 +0300 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-04-21 23:12:52 +0300 |
| commit | 3e99f5a75e14455aa75ca3a1dc7d08bd3e6f55fd (patch) | |
| tree | 568bb781c0d71bdea58f5dc7192e6c5edec94c27 /internal/image | |
| parent | d15846a2784d15c5043833874b6539b727555cb0 (diff) | |
u7: wire documented config knobs into runtime
Diffstat (limited to 'internal/image')
| -rw-r--r-- | internal/image/gemini.go | 28 | ||||
| -rw-r--r-- | internal/image/gemini_attribution.go | 6 | ||||
| -rw-r--r-- | internal/image/gemini_test.go | 28 | ||||
| -rw-r--r-- | internal/image/registry.go | 8 | ||||
| -rw-r--r-- | internal/image/types_test.go | 1 |
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 |
