Warning: session_start(): open(/tmp/sess_ab861b163a4f71f64dc27e5a7be031ef, O_RDWR) failed: No space left on device (28) in /home/mew1510/public_html/blog/include/GLOBAL.php on line 4

Warning: session_start(): Cannot send session cache limiter - headers already sent (output started at /home/mew1510/public_html/blog/include/GLOBAL.php:4) in /home/mew1510/public_html/blog/include/GLOBAL.php on line 4
Autogenerating Idol Desktop Backgrounds - Perfect Pop Star Academy

Autogenerating Idol Desktop Backgrounds

Konomi Baba background
Drunk Baba is best Baba

I'm a real big fan of those super minimalist anime backgrounds. As an Arch Linux user, I have a scary amount of control over every little aspect of my computer's interface, and I prefer clean, flat designs over the strange, over-the-top desktop designs associated with Linux a decade ago. I guess I want my system to scream "I don't waste resources on fancy eye candy!", and simple wallpapers help portray that philosophy.

Well, after years of using downloaded Sound Voltex, Serial Experiments Lain, and PriPara wallpapers, I finally decided that my desktop needed a fresh coat of paint. So... I made myself some iDOLM@STER Million Live desktop backgrounds, using the official art extracted from the game! But the idea of generating them myself seemed kind of painful... I mean, opening up the GIMP and using the paint bucket tool isn't hard, but I have a bunch of characters I like so I'd like to rotate between many different backgrounds. It seems pretty tedious, and adding a new idol would be pretty time consuming. After the first batch of backgrounds I'd probably get tired and swear never to add any more to the rotation.

So... I automated it! Creating a new desktop background is as simple as dropping the art into a folder with that idol's name. Everything else is generated automatically. And check out these cool results!

Kana Yabuki background Akane Nonohara background
Oh Like a Orange Sapphire☆passion!!!

So, let's talk about how to generate these backgrounds!

Part 1: Idol Data

Of course, to actually make such a process automatic, we need some kind of data source. Our utility has to actually know that Konomi Baba's color is #f1becb. I surely don't have the hex codes for character colors memorized either. Fortunately, I have all of that data recorded in my (needs to be better maintained) idolmaster-idols data project! Upon cloning the repository and compiling all the modules to one JSON file, I now have all the data necessary to generate backgrounds!

Arisa is excited about idols
I feel a bit like Arisa with my own idol database.

Part 2: Making Backgrounds

Here's how I have things set up. I have a source/ directory which contains subdirectories for all of our different idol images. Here's a picture of the directories I'm using:

baba_konomi  julia  kitakami_reika  nonohara_akane  shijou_takane  tokoro_megumi  yabuki_kana

Inside each folder I've just plopped in the transparent sprites extracted directly from the game:

89aab4334654281e417b1119a90d2bd44b7147dc56c6ce6d9c5ef0fd4861bd12.png 89aab4334654281e417b1119a90d2bd4761181d4fc851ccee3cd44c059828a11.png 89aab4334654281e417b1119a90d2bd4d11842353e9c41845a19a431d55ea5a2.png 89aab4334654281e417b1119a90d2bd4d8243c61b26fb0b7288fa54ac2b77c5f.png 89aab4334654281e417b1119a90d2bd4fedb0e33e682327cb71c99ea4101407f.png
(The filenames don't matter; these ones are taken directly from the game)

The Ruby script I have set up will scan these directories to know which idol it's making backgrounds of and will use each source image to make one desktop background. It's only a quick script so I'll be calling lots of external shell commands so it acts more like a "smart bash script" rather than pure Ruby, chunky bacon and all. Anyway, let's generate these things!

#!/usr/bin/ruby
require 'json';
FILE_PATH = "./idols.json"
screenx, screeny = `xrandr`.scan(/current (\d+) x (\d+)/).flatten.collect{|x| x.to_i}
 
idols = JSON.parse(File.read(FILE_PATH), object_class: OpenStruct);
dir_list = Dir["./source/*"]

I start off the script getting the current resolution of the screen using the output from the xrandr command. This part of the script is pretty much Linux-exclusive, but anyone wanting to make a similar script can either hardcode values or use techniques for their own platform to get the screen resolution automatically. Also you can see me loading up the idol JSON data and scanning my source/ directory for its subdirectories.

From there we loop over dir_list, which is an array of all of our idol subdirectories. And this is where the magick happens.

dir_list.each do |idol|
	idol_name = idol.gsub("./source/","")
	idol_color = idols[idol_name].color;
	`mkdir -p bgs/#{idol_name}`
	puts "Generating #{idols[idol_name].name.western} bg canvas..."
	`convert -size #{screenx}x#{screeny} canvas:#{idol_color} #{idol_name}.png`
	#More stuff will go in this loop later
end

I'm using ImageMagick command line tools to generate these backgrounds. The first thing we have to do is generate the blank color "slate" which we'll then be overlaying our various idol images over. The convert command generates that blank slate with our idol's color and saves it to a temporary file named after our idol.

From there we can loop over the idol's image files and start generating the backgrounds.

img_list = Dir["#{idol}/*.png"]
identifier = 0
img_list.each do |img|
	w,h = `identify -format "%[fx:w] %[fx:h]" #{img}`.split(" ").collect{|x| x.to_i}
	geometry_x = screenx-w
	geometry_y = screeny-h
	identifier+=1
	`convert #{idol_name}.png #{img} -geometry +#{geometry_x}+#{geometry_y} -composite ./bgs/#{idol_name}/#{identifier}.png`
end

I wanted the source images in the lower corner of each background, since it was the most aesthetically pleasing. Also because all the source images tend to cut off before the idols' feet so... they have to be on the bottom of the image. As a result, I use the ImageMagick identify command to grab the width and height of the source image so we can math them into the lower right corner of the screen. We just have to subtract the idol image's width and height from the colored slate's width and height to get the position to put them on the edge of the screen! You can also see I have an identifier variable which is just used to give them individually numbered filenames.

The results are great! Perfect minimalist backgrounds! And the game-ripped transparent files provide enough "padding" to the images so everything fits rather nicely.

Reika Kitakami background

That's just splendid! Here's an image of an Akane-chan background that's been—
... oh god...

Akane's hand is cut off
...Akane-chan's hand!!!

Part 3: A Problem

So... sometimes the Million Live art doesn't just cut off at legs. The high res art typically cuts off on the sides so the sprites sizes are consistent even when characters have big hair or large props. This is fine for our system right now when sprites are cut off on the right side, but when they're cut off on the left side things look pretty awkward. Is there a way to autodetect when an idol is cut off on the left so we can place them there?

The answer is... yes! Or at least I have code that seems to be able to do that. Here's the approach I'm taking: we use ImageMagick to analyze the left and right side of our source image and calculate how much foreground stuff is in it, comparing the quantities to determine whether the image belongs on the left or right side of the background. Here's a visualization of what the algorithm is doing:

Konomi holding a Tuba
(Note, the actual areas we're analyzing are only one pixel wide, they've just been exaggerated for illustration purposes)

We analyze those red and blue sections of the image to determine which one has more variety in pixels (in other words, which one has more foreground). In this case the right area has a higher standard deviation so the image belongs on the right side... which was already the default in our implementation... but if we flipped the image the algorithm would correctly identify the left side! Anyway, we use ImageMagick's convert command to crop the areas we're analyzing and then we use the identify command to get the standard deviation of each cropped section.

w,h = `identify -format "%[fx:w] %[fx:h]" #{img}`.split(" ").collect{|x| x.to_i}
puts "Detecting left edge special case..."
`convert -crop 1x#{h}+0x0 #{img} file_left_mask.png`
deviation = `identify -format %[standard-deviation] file_left_mask.png`.to_i
puts "Detecting right edge..."
`convert -crop 1x#{h}+#{w-1}x0 #{img} file_right_mask.png`
deviation_r = `identify -format %[standard-deviation] file_right_mask.png`.to_i
geometry_x = screenx-w
geometry_y = screeny-h
if deviation > 0 && deviation_r < deviation
	geometry_x = 0
end
identifier+=1
`convert #{idol_name}.png #{img} -geometry +#{geometry_x}+#{geometry_y} -composite ./bgs/#{idol_name}/#{identifier}.png`
puts "Cleaning up..."
`rm file_left_mask.png`
`rm file_right_mask.png`

That's the new inner loop for each source image. Because we ran those convert commands to get the slices we were analyzing, we have to clean up the files we generated after we're done. I just run the shell rm command since it's a bit easier in a simple script like this. I know some Ruby purists will probably hate me for that.

The final results are much better! Images generally show up on the side of the screen they belong— the only exception is when an image is cut off on both sides and there's no real fix for that. Still, because of the way the algorithm compares sides, it'll do its best to align it to the side that has more stuff cut off. Also, the code defaults to the right side if it detects nothing is cut off on the left side. This can easily be rewritten to do the opposite.

Megumi Tokoro background
Sweet! It works!

Cool! Our code actually works! Well... to the best of my knowledge anyway. All the backgrounds are saved in a bgs/ directory, named after each idol and individually numbered.

Part 4: There is No Part 4

Overall, generating these backgrounds is a pretty simple task. We do have to use a bit of "magick" to get everything working properly, and the algorithm to check which side the image needs to go on definitely surprised me a bit when I originally wrote this code! I think this is a fun demonstration of what you can make when using idol data like this! From nothing but a JSON file and these source images, we've created simple but nice desktop backgrounds!

And with this data we can even do more! Right now I have my computer set up to randomly select a new background every 30 minutes... but I wonder if I can set it up so that if it's an idol's birthday, only that idol will appear for that day?

Anyway, here's the final hacky and unpolished Ruby script that I happened to write:

#!/usr/bin/ruby
require 'json';
FILE_PATH = "./idols.json"
screenx, screeny = `xrandr`.scan(/current (\d+) x (\d+)/).flatten.collect{|x| x.to_i}
 
idols = JSON.parse(File.read(FILE_PATH), object_class: OpenStruct);
dir_list = Dir["./source/*"]
dir_list.each do |idol|
	idol_name = idol.gsub("./source/","")
	idol_color = idols[idol_name].color;
	`mkdir -p bgs/#{idol_name}`
	puts "Generating #{idols[idol_name].name.western} bg canvas..."
	`convert -size #{screenx}x#{screeny} canvas:#{idol_color} #{idol_name}.png`
 
	img_list = Dir["#{idol}/*.png"]
	identifier = 0
	img_list.each do |img|
		w,h = `identify -format "%[fx:w] %[fx:h]" #{img}`.split(" ").collect{|x| x.to_i}
		puts "Detecting left edge special case..."
		`convert -crop 1x#{h}+0x0 #{img} file_left_mask.png`
		deviation = `identify -format %[standard-deviation] file_left_mask.png`.to_i
		puts "Detecting right edge..."
		`convert -crop 1x#{h}+#{w-1}x0 #{img} file_right_mask.png`
		deviation_r = `identify -format %[standard-deviation] file_right_mask.png`.to_i
		geometry_x = screenx-w
		geometry_y = screeny-h
		if deviation > 0 && deviation_r < deviation
			geometry_x = 0
		end
		identifier+=1
		`convert #{idol_name}.png #{img} -geometry +#{geometry_x}+#{geometry_y} -composite ./bgs/#{idol_name}/#{identifier}.png`
		puts "Cleaning up..."
		`rm file_left_mask.png`
		`rm file_right_mask.png`
	end
 
	`rm #{idol_name}.png`
	puts "Cleaned up #{idols[idol_name].name.western} canvas files"
end

コメント

Join the discussion:
To leave a comment, you must sign in with Twitter.
Sign in with Twitter!


There are no comments. Why not leave one yourself?

Warning: Unknown: open(/tmp/sess_ab861b163a4f71f64dc27e5a7be031ef, O_RDWR) failed: No space left on device (28) in Unknown on line 0

Warning: Unknown: Failed to write session data (files). Please verify that the current setting of session.save_path is correct () in Unknown on line 0