Skip to content

Commit 10d28db

Browse files
committed
fix: correct tile format conversion flow and webp_config validation
- added `output_format` normalization in `src/config.ru` via `normalize_output_format` and switched conversion control to `route[:output_format]` instead of `metadata[:format]` - updated `fetch_http` (config.ru) to detect input format with `detect_image_format`, mark decoded `lerc` as `png`, and force `png` only when `output_format: png` is explicitly set - applied the same approach in `src/background_tile_loader.rb`: `output_format`/`normalize_output_format`, `detect_image_format`, and target-format-driven conversion instead of `source_format == 'png'` - removed silent fallback defaults in `convert_to_webp` (both files); added explicit errors for missing `webp_config.effort` (lossless) and `webp_config.quality` (lossy) - cleaned up `BackgroundTileLoader#output_format` formatting and extracted `normalize_output_format` for readability without behavior changes
1 parent b4ba9d6 commit 10d28db

2 files changed

Lines changed: 84 additions & 15 deletions

File tree

src/background_tile_loader.rb

Lines changed: 43 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -629,13 +629,45 @@ def tms_y(z, y)
629629
end
630630

631631
def convert_to_webp(data)
632-
webp_config = @route[:webp_config]
632+
webp_config = @route[:webp_config] || {}
633633
lossless = webp_config[:lossless].nil? ? true : webp_config[:lossless]
634-
params = lossless ? { lossless: true, effort: webp_config[:effort]} : { lossless: false, Q: webp_config[:quality]}
634+
params = if lossless
635+
effort = webp_config[:effort]
636+
raise ArgumentError, "webp_config.effort is required when lossless=true" if effort.nil?
637+
{ lossless: true, effort: effort }
638+
else
639+
quality = webp_config[:quality]
640+
raise ArgumentError, "webp_config.quality is required when lossless=false" if quality.nil?
641+
{ lossless: false, Q: quality }
642+
end
635643

636644
Vips::Image.new_from_buffer(data, '').write_to_buffer('.webp', **params)
637645
end
638646

647+
def output_format
648+
return @output_format if instance_variable_defined?(:@output_format)
649+
650+
raw_format = @route[:output_format]
651+
@output_format = normalize_output_format(raw_format)
652+
end
653+
654+
def normalize_output_format(raw_format)
655+
return nil if raw_format.nil?
656+
657+
format = raw_format.to_s.downcase
658+
raise ArgumentError, "Invalid output_format '#{format}' for #{@source_name}. Supported: png, webp" unless %w[png webp].include?(format)
659+
660+
format
661+
end
662+
663+
def detect_image_format(content_type)
664+
type = content_type.to_s.downcase
665+
return 'webp' if type.include?('image/webp')
666+
return 'png' if type.include?('image/png')
667+
668+
nil
669+
end
670+
639671
def update_status(zoom_level, status)
640672
@route[:db][:tile_scan_progress].where(source: @source_name, zoom_level: zoom_level).update(status: status)
641673
update_status_in_json(zoom_level, status)
@@ -818,6 +850,7 @@ def perform_tile_fetch(x, y, z)
818850
end
819851

820852
data = response.body
853+
current_format = detect_image_format(response.headers['content-type'])
821854

822855
if response.headers['content-encoding']&.include?('gzip')
823856
data = Zlib::GzipReader.new(StringIO.new(data)).read rescue data
@@ -846,6 +879,7 @@ def perform_tile_fetch(x, y, z)
846879
}
847880
end
848881
data = decoded
882+
current_format = 'png'
849883
rescue => e
850884
return {
851885
success: false,
@@ -868,22 +902,22 @@ def perform_tile_fetch(x, y, z)
868902
end
869903
end
870904

905+
target_format = output_format
906+
871907
if @route[:downsample_config]&.dig(:enabled) && data && !data.empty?
872908
begin
873909
encoding = @route[:metadata][:encoding]
874910
target_size = @route[:downsample_config][:target_size]
875911
method = @route[:downsample_config][:method]
876-
source_format = @route[:metadata][:format]
877-
output_format = @route[:metadata][:format]
878912

879-
if source_format == 'webp'
913+
if current_format == 'webp'
880914
img = Vips::Image.new_from_buffer(data, '')
881915
data = img.write_to_buffer('.png')
882916
end
883917

884918
data = TerrainDownsampleFFI.downsample_png(data, target_size, encoding, method)
885919

886-
if output_format == 'webp'
920+
if target_format == 'webp'
887921
data = convert_to_webp(data)
888922
end
889923
rescue => e
@@ -895,7 +929,7 @@ def perform_tile_fetch(x, y, z)
895929
body: data
896930
}
897931
end
898-
elsif @route[:webp_config] && @route[:source_format] == 'png'
932+
elsif target_format == 'webp'
899933
begin
900934
data = convert_to_webp(data)
901935
rescue => e
@@ -907,6 +941,8 @@ def perform_tile_fetch(x, y, z)
907941
body: data
908942
}
909943
end
944+
elsif target_format == 'png' && current_format != 'png'
945+
data = Vips::Image.new_from_buffer(data, '').write_to_buffer('.png')
910946
end
911947

912948
{ success: true, data: data }

src/config.ru

Lines changed: 41 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -304,8 +304,20 @@ def create_http_client(uri, route)
304304
end
305305
end
306306

307+
def normalize_output_format(route, route_name)
308+
raw_format = route[:output_format]
309+
return nil if raw_format.nil?
310+
311+
format = raw_format.to_s.downcase
312+
return format if %w[png webp].include?(format)
313+
314+
raise ArgumentError, "Invalid output_format '#{format}' for source '#{route_name}'. Supported: png, webp"
315+
end
316+
307317
configure do
308318
ROUTES.each do |_name, route|
319+
route[:output_format] = normalize_output_format(route, _name)
320+
309321
uri = URI.parse route[:target].gsub(/[{}]/, '_')
310322

311323
client = create_http_client(uri, route)
@@ -515,6 +527,7 @@ helpers do
515527
copy_headers_from_response(response.headers)
516528

517529
data = response.body
530+
current_format = detect_image_format(response.headers['content-type'])
518531

519532
if response.headers['content-encoding']&.include?('gzip')
520533
data = Zlib::GzipReader.new(StringIO.new(data)).read rescue data
@@ -533,27 +546,28 @@ helpers do
533546

534547
headers['Content-Type'] = 'image/png'
535548
data = decoded_data
549+
current_format = 'png'
536550
rescue => e
537551
return { error: true, reason: 'lerc_decode_error', details: build_error_details(response, "LERC decode error: #{e.message}"), status: 500, body: data }
538552
end
539553
end
540554

555+
target_format = route[:output_format]
556+
541557
if route[:downsample_config]&.dig(:enabled) && data && !data.empty?
542558
begin
543559
encoding = route[:metadata][:encoding]
544560
target_size = route[:downsample_config][:target_size]
545561
method = route[:downsample_config][:method]
546-
source_format = route[:metadata][:format]
547-
output_format = route[:metadata][:format]
548562

549-
if source_format == 'webp'
563+
if current_format == 'webp'
550564
img = Vips::Image.new_from_buffer(data, '')
551565
data = img.write_to_buffer('.png')
552566
end
553567

554568
data = TerrainDownsampleFFI.downsample_png(data, target_size, encoding, method)
555569

556-
if output_format == 'webp'
570+
if target_format == 'webp'
557571
data = convert_to_webp(data, route)
558572
headers['Content-Type'] = 'image/webp'
559573
else
@@ -562,13 +576,16 @@ helpers do
562576
rescue => e
563577
return { error: true, reason: 'image_processing_error', details: build_error_details(response, "Image processing error: #{e.message}"), status: 500, body: data }
564578
end
565-
elsif route[:webp_config] && route[:source_format] == 'png'
579+
elsif target_format == 'webp'
566580
begin
567581
data = convert_to_webp(data, route)
568582
headers['Content-Type'] = 'image/webp'
569583
rescue => e
570584
return { error: true, reason: 'webp_conversion_error', details: build_error_details(response, "WebP conversion error: #{e.message}"), status: 500, body: data }
571585
end
586+
elsif target_format == 'png' && current_format != 'png'
587+
data = Vips::Image.new_from_buffer(data, '').write_to_buffer('.png')
588+
headers['Content-Type'] = 'image/png'
572589
end
573590

574591
{ error: false, data: data }
@@ -609,14 +626,30 @@ helpers do
609626
end
610627

611628
otl_def def convert_to_webp(data, route)
612-
webp_config = route[:webp_config]
629+
webp_config = route[:webp_config] || {}
613630
lossless = webp_config[:lossless].nil? ? true : webp_config[:lossless]
614-
params = lossless ? { lossless: true, effort: webp_config[:effort]} : { lossless: false, Q: webp_config[:quality]}
631+
params = if lossless
632+
effort = webp_config[:effort]
633+
raise ArgumentError, "webp_config.effort is required when lossless=true" if effort.nil?
634+
{ lossless: true, effort: effort }
635+
else
636+
quality = webp_config[:quality]
637+
raise ArgumentError, "webp_config.quality is required when lossless=false" if quality.nil?
638+
{ lossless: false, Q: quality }
639+
end
615640

616641
otl_current_span { _1.add_attributes params }
617642
Vips::Image.new_from_buffer(data, '').write_to_buffer('.webp', **params)
618643
end
619644

645+
def detect_image_format(content_type)
646+
type = content_type.to_s.downcase
647+
return 'webp' if type.include?('image/webp')
648+
return 'png' if type.include?('image/png')
649+
650+
nil
651+
end
652+
620653
def build_request_headers(route)
621654
base_headers = {
622655
'Accept' => 'image/webp,image/apng,image/*,*/*;q=0.8',
@@ -692,4 +725,4 @@ at_exit do
692725
end
693726
end
694727

695-
run Sinatra::Application
728+
run Sinatra::Application

0 commit comments

Comments
 (0)