Improved speed of decoding/encoding images, added generating animated previews, fixed drawing clock hands
parent
027b9c266d
commit
3d0b01fe0d
|
@ -1,7 +1,7 @@
|
|||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio 15
|
||||
VisualStudioVersion = 15.0.27004.2009
|
||||
VisualStudioVersion = 15.0.27130.0
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WatchFace.Parser", "WatchFace.Parser\WatchFace.Parser.csproj", "{8D2E0224-DE7F-4EC9-AD23-9904B9D7B409}"
|
||||
EndProject
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System.Drawing;
|
||||
using BumpKit;
|
||||
using NLog;
|
||||
|
||||
namespace Resources.Image
|
||||
|
@ -10,45 +11,48 @@ namespace Resources.Image
|
|||
public static void Process(Bitmap image)
|
||||
{
|
||||
var isImageAltered = false;
|
||||
for (var y = 0; y < image.Height; y++)
|
||||
for (var x = 0; x < image.Width; x++)
|
||||
using (var context = image.CreateUnsafeContext())
|
||||
{
|
||||
var color = image.GetPixel(x, y);
|
||||
var colorError = new ColorError(color);
|
||||
|
||||
if (colorError.IsZero) continue;
|
||||
|
||||
if (!isImageAltered)
|
||||
for (var y = 0; y < context.Height; y++)
|
||||
for (var x = 0; x < context.Width; x++)
|
||||
{
|
||||
Logger.Warn(
|
||||
"Dithering applied for an image. Resource in watch face will use only supported colors. You can't get back original image by unpacking watch face."
|
||||
);
|
||||
isImageAltered = true;
|
||||
}
|
||||
var color = context.GetPixel(x, y);
|
||||
var colorError = new ColorError(color);
|
||||
|
||||
image.SetPixel(x, y, colorError.NewColor);
|
||||
if (colorError.IsZero) continue;
|
||||
|
||||
if (x + 1 < image.Width)
|
||||
{
|
||||
color = image.GetPixel(x + 1, y);
|
||||
image.SetPixel(x + 1, y, colorError.ApplyError(color, 7, 16));
|
||||
}
|
||||
|
||||
if (y + 1 < image.Height)
|
||||
{
|
||||
if (x > 1)
|
||||
if (!isImageAltered)
|
||||
{
|
||||
color = image.GetPixel(x - 1, y + 1);
|
||||
image.SetPixel(x - 1, y + 1, colorError.ApplyError(color, 3, 16));
|
||||
Logger.Warn(
|
||||
"Dithering applied for an image. Resource in watch face will use only supported colors. You can't get back original image by unpacking watch face."
|
||||
);
|
||||
isImageAltered = true;
|
||||
}
|
||||
|
||||
color = image.GetPixel(x, y + 1);
|
||||
image.SetPixel(x, y + 1, colorError.ApplyError(color, 5, 16));
|
||||
context.SetPixel(x, y, colorError.NewColor);
|
||||
|
||||
if (x < image.Width - 1)
|
||||
if (x + 1 < context.Width)
|
||||
{
|
||||
color = image.GetPixel(x + 1, y + 1);
|
||||
image.SetPixel(x + 1, y + 1, colorError.ApplyError(color, 1, 16));
|
||||
color = context.GetPixel(x + 1, y);
|
||||
context.SetPixel(x + 1, y, colorError.ApplyError(color, 7, 16));
|
||||
}
|
||||
|
||||
if (y + 1 < context.Height)
|
||||
{
|
||||
if (x > 1)
|
||||
{
|
||||
color = context.GetPixel(x - 1, y + 1);
|
||||
context.SetPixel(x - 1, y + 1, colorError.ApplyError(color, 3, 16));
|
||||
}
|
||||
|
||||
color = context.GetPixel(x, y + 1);
|
||||
context.SetPixel(x, y + 1, colorError.ApplyError(color, 5, 16));
|
||||
|
||||
if (x < context.Width - 1)
|
||||
{
|
||||
color = context.GetPixel(x + 1, y + 1);
|
||||
context.SetPixel(x + 1, y + 1, colorError.ApplyError(color, 1, 16));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
using System;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using BumpKit;
|
||||
using NLog;
|
||||
using Resources.Utils;
|
||||
|
||||
|
@ -79,15 +80,18 @@ namespace Resources.Image
|
|||
private Bitmap ReadImage()
|
||||
{
|
||||
var image = new Bitmap(_width, _height);
|
||||
for (var y = 0; y < _height; y++)
|
||||
using (var context = image.CreateUnsafeContext())
|
||||
{
|
||||
var rowBytes = _reader.ReadBytes(_rowLengthInBytes);
|
||||
var bitReader = new BitReader(rowBytes);
|
||||
for (var x = 0; x < _width; x++)
|
||||
for (var y = 0; y < _height; y++)
|
||||
{
|
||||
var pixelColorIndex = bitReader.ReadBits(_bitsPerPixel);
|
||||
var color = _palette[(int) pixelColorIndex];
|
||||
image.SetPixel(x, y, color);
|
||||
var rowBytes = _reader.ReadBytes(_rowLengthInBytes);
|
||||
var bitReader = new BitReader(rowBytes);
|
||||
for (var x = 0; x < _width; x++)
|
||||
{
|
||||
var pixelColorIndex = bitReader.ReadBits(_bitsPerPixel);
|
||||
var color = _palette[(int) pixelColorIndex];
|
||||
context.SetPixel(x, y, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
return image;
|
||||
|
|
|
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
|||
using System.Drawing;
|
||||
using System.Drawing.Imaging;
|
||||
using System.IO;
|
||||
using BumpKit;
|
||||
using NLog;
|
||||
using Resources.Utils;
|
||||
|
||||
|
@ -70,26 +71,30 @@ namespace Resources.Image
|
|||
private void ExtractPalette()
|
||||
{
|
||||
Logger.Trace("Extracting palette...");
|
||||
for (var y = 0; y < _height; y++)
|
||||
for (var x = 0; x < _width; x++)
|
||||
{
|
||||
var color = _image.GetPixel(x, y);
|
||||
if (_palette.Contains(color)) continue;
|
||||
|
||||
if (color.A < 0x80 && _transparency == 0)
|
||||
using (var context = _image.CreateUnsafeContext())
|
||||
{
|
||||
for (var y = 0; y < _height; y++)
|
||||
for (var x = 0; x < _width; x++)
|
||||
{
|
||||
Logger.Trace("Palette item {0}: R {1:X2}, G {2:X2}, B {3:X2}, Transaparent color",
|
||||
_palette.Count, color.R, color.G, color.B
|
||||
);
|
||||
_palette.Insert(0, color);
|
||||
_transparency = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Trace("Palette item {0}: R {1:X2}, G {2:X2}, B {3:X2}",
|
||||
_palette.Count, color.R, color.G, color.B
|
||||
);
|
||||
_palette.Add(color);
|
||||
var color = context.GetPixel(x, y);
|
||||
if (_palette.Contains(color)) continue;
|
||||
|
||||
if (color.A < 0x80 && _transparency == 0)
|
||||
{
|
||||
Logger.Trace("Palette item {0}: R {1:X2}, G {2:X2}, B {3:X2}, Transaparent color",
|
||||
_palette.Count, color.R, color.G, color.B
|
||||
);
|
||||
_palette.Insert(0, color);
|
||||
_transparency = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Trace("Palette item {0}: R {1:X2}, G {2:X2}, B {3:X2}",
|
||||
_palette.Count, color.R, color.G, color.B
|
||||
);
|
||||
_palette.Add(color);
|
||||
}
|
||||
}
|
||||
}
|
||||
_paletteColors = (ushort) _palette.Count;
|
||||
|
@ -136,26 +141,29 @@ namespace Resources.Image
|
|||
i++;
|
||||
}
|
||||
|
||||
for (var y = 0; y < _height; y++)
|
||||
using (var context = _image.CreateUnsafeContext())
|
||||
{
|
||||
var rowData = new byte[_rowLengthInBytes];
|
||||
var memoryStream = new MemoryStream(rowData);
|
||||
var bitWriter = new BitWriter(memoryStream);
|
||||
for (var x = 0; x < _width; x++)
|
||||
for (var y = 0; y < _height; y++)
|
||||
{
|
||||
var color = _image.GetPixel(x, y);
|
||||
if (color.A < 0x80 && _transparency == 1)
|
||||
var rowData = new byte[_rowLengthInBytes];
|
||||
var memoryStream = new MemoryStream(rowData);
|
||||
var bitWriter = new BitWriter(memoryStream);
|
||||
for (var x = 0; x < _width; x++)
|
||||
{
|
||||
bitWriter.WriteBits(0, _bitsPerPixel);
|
||||
}
|
||||
else
|
||||
{
|
||||
var paletteIndex = paletteHash[color];
|
||||
bitWriter.WriteBits(paletteIndex, _bitsPerPixel);
|
||||
var color = context.GetPixel(x, y);
|
||||
if (color.A < 0x80 && _transparency == 1)
|
||||
{
|
||||
bitWriter.WriteBits(0, _bitsPerPixel);
|
||||
}
|
||||
else
|
||||
{
|
||||
var paletteIndex = paletteHash[color];
|
||||
bitWriter.WriteBits(paletteIndex, _bitsPerPixel);
|
||||
}
|
||||
}
|
||||
bitWriter.Flush();
|
||||
_writer.Write(rowData);
|
||||
}
|
||||
bitWriter.Flush();
|
||||
_writer.Write(rowData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,5 +31,4 @@ using System.Runtime.InteropServices;
|
|||
// You can specify all the values or you can default the Build and Revision Numbers
|
||||
// by using the '*' as shown below:
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("1.0.1.0")]
|
||||
[assembly: AssemblyFileVersion("1.0.1.0")]
|
||||
[assembly: AssemblyVersion("1.0.*")]
|
|
@ -31,6 +31,9 @@
|
|||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="BumpKit, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Bumpkit.1.0.2\lib\BumpKit.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="NLog, Version=4.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\NLog.4.4.12\lib\net40\NLog.dll</HintPath>
|
||||
</Reference>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<packages>
|
||||
<package id="Bumpkit" version="1.0.2" targetFramework="net40-client" />
|
||||
<package id="NLog" version="4.4.12" targetFramework="net40-client" />
|
||||
</packages>
|
|
@ -1,6 +1,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Drawing2D;
|
||||
using System.Linq;
|
||||
using WatchFace.Parser.Interfaces;
|
||||
|
||||
|
@ -29,7 +30,7 @@ namespace WatchFace.Parser.Models.Elements.Common
|
|||
}
|
||||
else
|
||||
{
|
||||
drawer.FillPolygon(new SolidBrush(Color), points);
|
||||
drawer.FillPolygon(new SolidBrush(Color), points, FillMode.Alternate);
|
||||
drawer.DrawPolygon(new Pen(Color, 1), points);
|
||||
}
|
||||
|
||||
|
@ -40,8 +41,8 @@ namespace WatchFace.Parser.Models.Elements.Common
|
|||
{
|
||||
var radians = degrees / 180 * Math.PI;
|
||||
var x = element.X * Math.Cos(radians) + element.Y * Math.Sin(radians);
|
||||
var y = element.X * Math.Sin(radians) + element.Y * Math.Cos(radians);
|
||||
return new Point((int) Math.Round(x + Center.X), (int) Math.Round(y + Center.Y));
|
||||
var y = element.X * Math.Sin(radians) - element.Y * Math.Cos(radians);
|
||||
return new Point((int) Math.Floor(x + Center.X), (int) Math.Floor(y + Center.Y));
|
||||
}
|
||||
|
||||
protected override Element CreateChildForParameter(Parameter parameter)
|
||||
|
|
|
@ -15,18 +15,19 @@ namespace WatchFace.Parser.Models.Elements
|
|||
|
||||
public void Draw(Graphics drawer, Bitmap[] resources, WatchState state)
|
||||
{
|
||||
if (state.CurrentWeather != WeatherCondition.Unknown)
|
||||
if (state.CurrentWeather != WeatherCondition.Unknown && Current != null)
|
||||
drawer.DrawImage(LoadWeatherImage(state.CurrentWeather), Current.X, Current.Y);
|
||||
else if (state.TodayWeather != WeatherCondition.Unknown)
|
||||
else if (state.TodayWeather != WeatherCondition.Unknown && Today != null)
|
||||
drawer.DrawImage(LoadWeatherImage(state.TodayWeather), Today.X, Today.Y);
|
||||
else if (state.TomorrowWeather != WeatherCondition.Unknown)
|
||||
else if (state.TomorrowWeather != WeatherCondition.Unknown && Tomorrow != null)
|
||||
drawer.DrawImage(LoadWeatherImage(state.TomorrowWeather), Tomorrow.X, Tomorrow.Y);
|
||||
}
|
||||
|
||||
private static Bitmap LoadWeatherImage(WeatherCondition weather)
|
||||
{
|
||||
var appLocation = System.IO.Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
|
||||
return (Bitmap) Image.FromFile(System.IO.Path.Combine(appLocation, "WeatherIcons", $"{(int) weather}.png"));
|
||||
var assembly = Assembly.GetExecutingAssembly();
|
||||
var imageStream = assembly.GetManifestResourceStream($"WatchFace.Parser.WeatherIcons.{(int) weather}.png");
|
||||
return (Bitmap) Image.FromStream(imageStream);
|
||||
}
|
||||
|
||||
protected override Element CreateChildForParameter(Parameter parameter)
|
||||
|
|
|
@ -0,0 +1,63 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using BumpKit;
|
||||
using WatchFace.Parser.Interfaces;
|
||||
using WatchFace.Parser.Models;
|
||||
|
||||
namespace WatchFace.Parser
|
||||
{
|
||||
public class PreviewGenerator
|
||||
{
|
||||
public static void CreateGif(List<Parameter> descriptor, Bitmap[] images, Stream outputStream)
|
||||
{
|
||||
var previewWatchFace = new Models.Elements.WatchFace(descriptor);
|
||||
var watchState = new WatchState();
|
||||
var time = watchState.Time;
|
||||
|
||||
using (var encoder = new GifEncoder(outputStream))
|
||||
{
|
||||
for (var i = 0; i < 10; i++)
|
||||
{
|
||||
var num = i + 1;
|
||||
watchState.BatteryLevel = num * 10;
|
||||
|
||||
watchState.Pulse = 60 + num * 2;
|
||||
watchState.Steps = num * 1000;
|
||||
watchState.Calories = num * 75;
|
||||
watchState.Distance = num * 700;
|
||||
|
||||
watchState.Bluetooth = num > 1 && num < 6;
|
||||
watchState.Unlocked = num > 2 && num < 7;
|
||||
watchState.Alarm = num > 3 && num < 8;
|
||||
watchState.DoNotDisturb = num > 4 && num < 9;
|
||||
|
||||
watchState.DayTemperature += 2;
|
||||
watchState.NightTemperature += 4;
|
||||
watchState.CurrentTemperature += 3;
|
||||
|
||||
watchState.Time = new DateTime(time.Year, num, num * 2 + 5, i * 2, i * 6, i);
|
||||
using (var image = CreateFrame(previewWatchFace, images, watchState))
|
||||
{
|
||||
encoder.AddFrame(image, frameDelay: TimeSpan.FromSeconds(1));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static Image CreateImage(IEnumerable<Parameter> descriptor, Bitmap[] resources)
|
||||
{
|
||||
var previewWatchFace = new Models.Elements.WatchFace(descriptor);
|
||||
return CreateFrame(previewWatchFace, resources, new WatchState());
|
||||
}
|
||||
|
||||
private static Image CreateFrame(IDrawable watchFace, Bitmap[] resources, WatchState state)
|
||||
{
|
||||
var preview = new Bitmap(176, 176);
|
||||
var graphics = Graphics.FromImage(preview);
|
||||
watchFace.Draw(graphics, resources, state);
|
||||
return preview;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -31,5 +31,4 @@ using System.Runtime.InteropServices;
|
|||
// You can specify all the values or you can default the Build and Revision Numbers
|
||||
// by using the '*' as shown below:
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("1.0.1.0")]
|
||||
[assembly: AssemblyFileVersion("1.0.1.0")]
|
||||
[assembly: AssemblyVersion("1.0.*")]
|
|
@ -31,6 +31,9 @@
|
|||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="BumpKit, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Bumpkit.1.0.2\lib\BumpKit.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Newtonsoft.Json, Version=10.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Newtonsoft.Json.10.0.3\lib\net40\Newtonsoft.Json.dll</HintPath>
|
||||
</Reference>
|
||||
|
@ -148,6 +151,7 @@
|
|||
<Compile Include="Models\WatchState.cs" />
|
||||
<Compile Include="Models\AirCondition.cs" />
|
||||
<Compile Include="Models\WeatherCondition.cs" />
|
||||
<Compile Include="PreviewGenerator.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="Reader.cs" />
|
||||
<Compile Include="Attributes\ParameterImagesCountAttribute.cs" />
|
||||
|
@ -169,12 +173,12 @@
|
|||
<None Include="packages.config" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="WeatherIcons\247.png">
|
||||
<EmbeddedResource Include="WeatherIcons\247.png">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="WeatherIcons\279.png">
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Include="WeatherIcons\279.png">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
<ItemGroup />
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<packages>
|
||||
<package id="Bumpkit" version="1.0.2" targetFramework="net40-client" />
|
||||
<package id="Newtonsoft.Json" version="10.0.3" targetFramework="net40-client" />
|
||||
<package id="NLog" version="4.4.12" targetFramework="net40-client" />
|
||||
</packages>
|
|
@ -9,7 +9,7 @@ using NLog.Config;
|
|||
using NLog.Targets;
|
||||
using Resources;
|
||||
using Resources.Models;
|
||||
using WatchFace.Parser.Models;
|
||||
using WatchFace.Parser;
|
||||
using WatchFace.Parser.Utils;
|
||||
using Reader = WatchFace.Parser.Reader;
|
||||
using Writer = WatchFace.Parser.Writer;
|
||||
|
@ -126,13 +126,13 @@ namespace WatchFace
|
|||
var watchFace = ParseResources(reader);
|
||||
if (watchFace == null) return;
|
||||
|
||||
Logger.Debug("Generating preview...");
|
||||
var previewWatchFace = new Parser.Models.Elements.WatchFace(reader.Parameters);
|
||||
|
||||
var preview = new Bitmap(176, 176);
|
||||
var graphics = Graphics.FromImage(preview);
|
||||
previewWatchFace.Draw(graphics, reader.Images.ToArray(), new WatchState());
|
||||
preview.Save(Path.Combine(outputDirectory, $"{baseName}.png"), ImageFormat.Png);
|
||||
Logger.Debug("Generating previews...");
|
||||
var preview = PreviewGenerator.CreateImage(reader.Parameters, reader.Images.ToArray());
|
||||
preview.Save(Path.Combine(outputDirectory, $"{baseName}_preview.png"), ImageFormat.Png);
|
||||
using (var gifOutput = File.OpenWrite(Path.Combine(outputDirectory, $"{baseName}_preview.gif")))
|
||||
{
|
||||
PreviewGenerator.CreateGif(reader.Parameters, reader.Images.ToArray(), gifOutput);
|
||||
}
|
||||
|
||||
Logger.Debug("Exporting resources to '{0}'", outputDirectory);
|
||||
var reDescriptor = new FileDescriptor {Images = reader.Images};
|
||||
|
@ -200,12 +200,12 @@ namespace WatchFace
|
|||
var descriptor = ParametersConverter.Build(watchFace);
|
||||
|
||||
Logger.Debug("Generating preview...");
|
||||
var previewWatchFace = new Parser.Models.Elements.WatchFace(descriptor);
|
||||
|
||||
var preview = new Bitmap(176, 176);
|
||||
var graphics = Graphics.FromImage(preview);
|
||||
previewWatchFace.Draw(graphics, imagesReader.Images.ToArray(), new WatchState());
|
||||
var preview = PreviewGenerator.CreateImage(descriptor, imagesReader.Images.ToArray());
|
||||
preview.Save(Path.ChangeExtension(outputFileName, ".png"));
|
||||
using (var gifOutput = File.OpenWrite(Path.ChangeExtension(outputFileName, ".gif")))
|
||||
{
|
||||
PreviewGenerator.CreateGif(descriptor, imagesReader.Images.ToArray(), gifOutput);
|
||||
}
|
||||
|
||||
Logger.Debug("Writing watch face to '{0}'", outputFileName);
|
||||
using (var fileStream = File.OpenWrite(outputFileName))
|
||||
|
@ -218,6 +218,7 @@ namespace WatchFace
|
|||
catch (Exception e)
|
||||
{
|
||||
Logger.Fatal(e.Message);
|
||||
File.Delete(outputFileName);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -31,5 +31,4 @@ using System.Runtime.InteropServices;
|
|||
// You can specify all the values or you can default the Build and Revision Numbers
|
||||
// by using the '*' as shown below:
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("1.0.1.0")]
|
||||
[assembly: AssemblyFileVersion("1.0.1.0")]
|
||||
[assembly: AssemblyVersion("1.0.*")]
|
Loading…
Reference in New Issue