//
// Copyright (C) 2008-2009 Jordi Mas i Hernandez, jmas@softcatala.org
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
using System;
using System.Runtime.InteropServices;
using System.Text;
using System.IO;
using System.Collections.Generic;
using Mistelix.DataModel;
using Mistelix.Core;
namespace Mistelix.Backends
{
//
// Generates XML file for Spumux command line tool
//
public class Spumux
{
const string menu_normal = "menu_normal.png";
const string menu_highlight = "menu_highlight.png";
Project project;
public Spumux (Project project)
{
this.project = project;
}
public void Create ()
{
StringBuilder sb = new StringBuilder (2048);
sb.Append ("\n");
sb.Append (" \n");
sb.Append (" \n");
sb.Append (GenerateMenus ());
sb.Append (" \n");
sb.Append (" \n");
sb.Append (" \n");
try {
using (FileStream fs = File.Create (project.FileToFullPath (Defines.SPUMUX_FILE)))
{
StreamWriter sw = new StreamWriter (fs);
sw.Write (sb.ToString ());
sw.Close ();
}
}
catch (IOException) {
Logger.Error ("Spumux.Create. Error accessing file {0}", project.FileToFullPath (Defines.SPUMUX_FILE));
}
}
public void Destroy ()
{
if (Mistelix.Debugging == false)
{
File.Delete (project.FileToFullPath (menu_normal));
File.Delete (project.FileToFullPath (menu_highlight));
File.Delete (project.FileToFullPath (Defines.SPUMUX_FILE));
}
}
void DrawButtons (Button button, Cairo.Context high, Cairo.Context normal)
{
// The coordinates should match what we provide to Spumux
DrawImageFromFile (normal, Path.Combine (Defines.DATA_DIR, project.Details.Theme.ButtonSelect),
button.X, button.Y, button.DrawingBoxWidth, button.DrawingBoxHeight);
DrawImageFromFile (high, Path.Combine (Defines.DATA_DIR, project.Details.Theme.ButtonHighlight),
button.X, button.Y, button.DrawingBoxWidth, button.DrawingBoxHeight);
}
public void DrawImageFromFile (Cairo.Context cr, string filename, double x, double y, double width, double height)
{
SvgImage image;
if (width <= 0 || height <= 0)
throw new ArgumentException (String.Format ("Spumux->DrawImageFromFile. DVD menu buttons width {0} and height {1} should be > 0", width, height));
try {
image = new SvgImage (filename);
}
catch (Exception)
{
Logger.Error ("Spumux.DrawImageFromFile. Error loading file {0}", filename);
return;
}
Logger.Debug ("Spumux.DrawImageFromFile. Loaded file {0}, w:{1} h:{2}", filename, image.Width, image.Height);
cr.Save ();
// Have to clip for RenderToCairo
cr.Rectangle (x, y, width, height);
cr.Clip ();
cr.Translate (x, y);
cr.Scale (width / image.Width, height / image.Height);
image.RenderToCairo (cr.Handle);
cr.Restore ();
image.Dispose ();
}
string GenerateMenus ()
{
StringBuilder sb = new StringBuilder (2048);
Cairo.ImageSurface shigh = new Cairo.ImageSurface (Cairo.Format.ARGB32, project.Details.Width, project.Details.Height);
Cairo.Context chight = new Cairo.Context (shigh);
Cairo.ImageSurface snormal = new Cairo.ImageSurface (Cairo.Format.ARGB32, project.Details.Width, project.Details.Height);
Cairo.Context cnormal = new Cairo.Context (snormal);
for (int i = 0; i < project.Buttons.Count; i++)
{
Button button = (Button)project.Buttons [i];
sb.Append (" \n");
DrawButtons (button, chight, cnormal);
}
// Quantize the images given
{
// See: http://www.cairographics.org/manual/bindings-surfaces.html
byte [] pixels_normal = QuantizeSurface (snormal);
Cairo.ImageSurface snormal_quantized = new Cairo.ImageSurface (ref pixels_normal, snormal.Format, snormal.Width, snormal.Height, snormal.Stride);
byte [] pixels_high = QuantizeSurface (shigh);
Cairo.ImageSurface shigh_quantized = new Cairo.ImageSurface (ref pixels_high, shigh.Format, shigh.Width, shigh.Height, shigh.Stride);
snormal_quantized.WriteToPng (project.FileToFullPath (menu_normal));
shigh_quantized.WriteToPng (project.FileToFullPath (menu_highlight));
snormal_quantized.Destroy ();
shigh_quantized.Destroy ();
}
if (Mistelix.Debugging)
{
snormal.WriteToPng (project.FileToFullPath ("menu_normal_unquantized.png"));
shigh.WriteToPng (project.FileToFullPath ("menu_highlight_unquantized.png"));
}
((IDisposable)cnormal).Dispose ();
((IDisposable)chight).Dispose ();
shigh.Destroy ();
snormal.Destroy ();
return sb.ToString ();
}
// The images used by spumux can each have a maximum of 4 colors (reality is actually 3) (with four you get 'ERR: Cannot pick button masks')
// We do a color quantization using a popularity algorithm by finding the 3 most frequently appearing colors in the original image.
// and setting every pixel to one of these frequently appearing colors
byte[] QuantizeSurface (Cairo.ImageSurface surface)
{
const int palete_size = 3;
byte[] pixels = surface.Data;
byte[] target = new byte [pixels.Length];
uint pixel;
Dictionary table = new Dictionary ();
// 1. Create a list of the pixels and how popular they are
for (int i = 0 ; i < pixels.Length; i = i + 4)
{
pixel = (uint) ((pixels[i + 0]) & 0xff);
pixel += (uint) ((pixels[i + 1] << 8) & 0xff00);
pixel += (uint) ((pixels[i + 2] << 16) & 0xff0000);
pixel += (uint) ((pixels[i + 3] << 24) & 0xff000000);
if (table.ContainsKey (pixel) == false) {
table.Add (pixel, 1);
} else
{
int times = table[pixel];
times++;
table[pixel] = times;
}
}
Logger.Debug ("Spumux.QuantizeSurface. Phase 1. Values: {0}", table.Count);
// 2. Select the most N most popular elements to build the palete
uint [] palete = new uint [palete_size];
int [] popularity = new int [palete_size];
foreach (KeyValuePair kvp in table)
{
int popular = kvp.Value;
int insert_pos = -1;
for (int i = 0; i < popularity.Length; i++)
{
if (popular > popularity[i]) {
if (insert_pos == -1 || popularity[insert_pos] < popularity[i])
insert_pos = i;
}
}
if (insert_pos != -1) {
popularity [insert_pos] = popular;
palete [insert_pos ] = kvp.Key;
}
}
for (int i = 0; i > 8) & 0xff);
dist += val * val;
val = pixels[i + 2] - (byte) ((palete[p] >> 16) & 0xff);
dist += val * val;
val = pixels[i + 3] - (byte) ((palete[p] >> 24) & 0xff);
dist += val * val;
dist = (float) Math.Sqrt (dist);
if (p == 0 || dist < better) {
better = dist;
element = p;
}
}
target [i + 0] = (byte) ((palete[element] & 0xff));
target [i + 1] = (byte) ((palete[element] >> 8) & 0xff);
target [i + 2] = (byte) ((palete[element] >> 16) & 0xff);
target [i + 3] = (byte) ((palete[element] >> 24) & 0xff);
}
return target;
}
}
}