Skip to content

Commit

Permalink
fixed BMP thumbnail extraction for non-Windows OSes
Browse files Browse the repository at this point in the history
  • Loading branch information
salaros committed Oct 18, 2023
1 parent 0c6fa6f commit 42d7236
Show file tree
Hide file tree
Showing 11 changed files with 141 additions and 134 deletions.
5 changes: 4 additions & 1 deletion src/CodeCave.Revit.Toolkit.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

<PropertyGroup>
<TargetFrameworks>netstandard2.0;net45</TargetFrameworks>
<LangVersion>latest</LangVersion>
<WarningLevel>7</WarningLevel>
<AppendTargetFrameworkToOutputPath Condition="'$(TargetFrameworks)' != ''">true</AppendTargetFrameworkToOutputPath>
</PropertyGroup>

Expand Down Expand Up @@ -29,14 +31,15 @@
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
<Product>$(AssemblyName)</Product>
<PackageProjectUrl>$(RepositoryUrl)</PackageProjectUrl>
<AutoGenerateBindingRedirects>False</AutoGenerateBindingRedirects>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="CodeCave.Extensions" Version="1.1.*" />
<PackageReference Include="CsvHelper" Version="18.0.*" />
<PackageReference Include="OpenMcdf" Version="2.3.*" />
<PackageReference Include="System.ComponentModel.Annotations" Version="$(NetCorePackageVersion)" />
<PackageReference Include="System.Drawing.Common" Version="7.0.*" Condition="'$(TargetFramework)' == 'netstandard2.0' Or '$(TargetFramework)' == 'netcoreapp2.1'" />
<PackageReference Include="System.Drawing.Common" Version="5.0.*" Condition="'$(TargetFramework)' == 'netstandard2.0' Or '$(TargetFramework)' == 'netcoreapp2.1'" />
<PackageReference Include="System.Text.Encoding.CodePages" Version="6.0.*" Condition="'$(TargetFramework)' != 'net45'" />
</ItemGroup>

Expand Down
3 changes: 0 additions & 3 deletions src/OLE/RevitFileInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -122,8 +122,6 @@ public static int GetFormatFromFile(string filePath)
/// Gets a <see cref="RevitFileInfo" /> instance from the given file path.
/// </summary>
/// <param name="filePath">The file path.</param>
/// <param name="readProperties">if set to <c>true</c> [read properties].</param>
/// <param name="readTypes">if set to <c>true</c> [read types].</param>
/// <returns></returns>
/// <exception cref="T:System.ArgumentException">filePath is invalid.</exception>
public static RevitFileInfo GetFromFile(string filePath)
Expand Down Expand Up @@ -174,7 +172,6 @@ public static RevitFileInfo GetFromFile(string filePath)
/// <summary>
/// Gets the properties.
/// </summary>
/// <param name="filePath">The file path.</param>
/// <returns></returns>
internal static Dictionary<string, string> GetProperties(byte[] basicInfo)
{
Expand Down
7 changes: 4 additions & 3 deletions src/OLE/Xml/ParameterList.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Xml;
using System.Xml.Schema;
Expand Down Expand Up @@ -29,9 +29,10 @@ public void ReadXml(XmlReader reader)
var unitSymbol = reader.GetAttribute("units").TryGetFromSymbol(out var unitSymbolType)
? unitSymbolType
: UnitSymbolType.UST_NONE;

var units = unitSymbol.TryGetFromUnitSymbol(out var displayUnitType)
? displayUnitType
: DisplayUnitType.DUT_CUSTOM;
? displayUnitType
: DisplayUnitType.DUT_CUSTOM;

var partAtomParam = new PartAtomParameter(reader.Name, paramType, group, units)
{
Expand Down
2 changes: 1 addition & 1 deletion src/OLE/Xml/PartAtomParameter.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using CodeCave.Revit.Toolkit.Parameters;
using CodeCave.Revit.Toolkit.Parameters;
using System;
using System.Diagnostics;
using System.Linq;
Expand Down
6 changes: 3 additions & 3 deletions src/Parameters/Catalog/TypeCatalogFile.ParameterDefinition.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System;
using System;
using System.Diagnostics;
using System.Linq;

Expand Down Expand Up @@ -37,8 +37,8 @@ public ParameterDefinition
(
string name,
ParameterType type,
DisplayUnitType displayUnits)
{
DisplayUnitType displayUnits
){
Name = !string.IsNullOrWhiteSpace(name) ? name : throw new ArgumentException(nameof(name));
ParameterType = (ParameterType.Invalid != type) ? type : throw new ArgumentException(nameof(type));
DisplayUnitType = (DisplayUnitType.DUT_UNDEFINED != displayUnits) ? displayUnits : throw new ArgumentException(nameof(displayUnits));
Expand Down
4 changes: 3 additions & 1 deletion src/Parameters/Catalog/TypeCatalogFile.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
Expand Down Expand Up @@ -86,9 +86,11 @@ public TypeCatalogFile(string typeCatalogFile, Encoding encoding = null)
var units = headerParts.ElementAt(1).TryGetUnitTypeFromCatalogString(out var unitType)
? unitType
: UnitType.UT_Undefined;

var type = units.TryGetParameterType(out var paramType)
? paramType
: ParameterType.Invalid;

var displayUnits = headerParts.ElementAt(2).TryGetFromCatalogString(out var displayUnitType)
? displayUnitType
: DisplayUnitType.DUT_UNDEFINED;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -253,10 +253,8 @@ public ParameterCollection(SharedParameterFile parameterFile, IEnumerable<Parame
public new void Add(ParameterDefinition parameter)
{
if (parameter == null) throw new ArgumentNullException(nameof(parameter));
if (parameter.Group == null) throw new ArgumentNullException(
nameof(parameter), $"Parameter you have provided has no {nameof(ParameterDefinition.Group)} assigned");
if (parameter.Guid == null) throw new ArgumentNullException(
nameof(parameter), $"Parameter you have provided has no {nameof(ParameterDefinition.Guid)} assigned");
if (parameter.Group == null) throw new ArgumentNullException(nameof(parameter), $"Parameter you have provided has no {nameof(ParameterDefinition.Group)} assigned");
if (parameter.Guid == null) throw new ArgumentNullException(nameof(parameter), $"Parameter you have provided has no {nameof(ParameterDefinition.Guid)} assigned");

if (this.Any(p => Equals(parameter.Guid, p.Guid))) throw new ArgumentException(
nameof(parameter),
Expand Down
158 changes: 118 additions & 40 deletions src/Thumbnails/DwgThumbnailExtractor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,13 @@ namespace CodeCave.Revit.Toolkit.Thumbnails
/// </summary>
public class DwgThumbnailExtractor : ThumbnailExtractor
{
private static readonly Color ColorBlack = Color.Black;

private static readonly Color ColorWhite = Color.White;

private bool RetainBackgroudColor { get; set; } = true;

/// <inheritdoc/>
public override byte[] ExtractThumbnailBytes(Stream stream)
{
using var binaryReader = new BinaryReader(stream);
Expand All @@ -24,51 +31,102 @@ public override byte[] ExtractThumbnailBytes(Stream stream)

for (short i = 1; i <= byteCount; i++)
{
var imageCode = binaryReader.ReadByte();
var imageHeaderStart = binaryReader.ReadInt32();
var imageHeaderSize = binaryReader.ReadInt32();
var thumbInfo = new ThumbnailInfo
{
Type = binaryReader.ReadByte(),
Start = binaryReader.ReadInt32(),
Length = binaryReader.ReadInt32(),
};

#pragma warning disable CC0120 // default just skips to the next byte
switch (imageCode)
switch (thumbInfo.Type)
{
// PNG thumbnail - ACAD 2013 and later
case ThumbnailImageCodes.PNG:
case ThumbnailInfo.PngType:
var ms2013 = new MemoryStream();
stream.Seek(imageHeaderStart, SeekOrigin.Begin);
stream.CopyTo(ms2013, imageHeaderSize);
stream.Seek(thumbInfo.Start, SeekOrigin.Begin);
stream.CopyTo(ms2013, thumbInfo.Length);
var byte2013 = ms2013.ToArray();

return byte2013;

// BMP Preview (2010 file format and lower)
case ThumbnailImageCodes.BMP:
binaryReader.ReadBytes(0xe);
var biBitCount = binaryReader.ReadUInt16();
binaryReader.ReadBytes(4);
var biSizeImage = binaryReader.ReadUInt32();

stream.Seek(imageHeaderStart, SeekOrigin.Begin);
var bitmapBuffer = binaryReader.ReadBytes(imageHeaderSize);
var bitCountRaw = (biBitCount < 9) ? 4 * Math.Pow(2, biBitCount) : 0;
var bitCount = Math.Truncate(bitCountRaw);
var colorTableSize = Convert.ToUInt32(bitCount);
using (var ms = new MemoryStream())
case ThumbnailInfo.BmpType:

for (var intCnt = 0; intCnt < byteCount; intCnt++)
{
using var binaryWriter = new BinaryWriter(ms);
binaryWriter.Write(Convert.ToUInt16(0x4d42));
binaryWriter.Write(54u + colorTableSize + biSizeImage);
binaryWriter.Write(default(ushort));
binaryWriter.Write(default(ushort));
binaryWriter.Write(54u + colorTableSize);
binaryWriter.Write(bitmapBuffer);

using var imageTmp2010 = new Bitmap(ms);
var bytes2010 = ToByteArray(imageTmp2010, ImageFormat.Png);

return bytes2010;
stream.Seek(thumbInfo.Start, SeekOrigin.Begin);
var udtHeader = new BitmapHeader
{
Size = binaryReader.ReadInt32(),
Width = binaryReader.ReadInt32(),
Height = binaryReader.ReadInt32(),
Planes = binaryReader.ReadInt16(),
BitCount = binaryReader.ReadInt16(),
Compression = binaryReader.ReadInt32(),
SizeImage = binaryReader.ReadInt32(),
XPelsPerMeter = binaryReader.ReadInt32(),
YPelsPerMeter = binaryReader.ReadInt32(),
ClrUsed = binaryReader.ReadInt32(),
ClrImportant = binaryReader.ReadInt32(),
};

var bytBMPBuff = new byte[thumbInfo.Length + 1];

if (udtHeader.BitCount != 8)
{
continue;
}

var udtColors = new RgbQuad[256];
for (int count = 0; count < 256; count++)
{
udtColors[count].Blue = binaryReader.ReadByte();
udtColors[count].Green = binaryReader.ReadByte();
udtColors[count].Red = binaryReader.ReadByte();
udtColors[count].Reserved = binaryReader.ReadByte();
}
stream.Seek(thumbInfo.Start - 1, SeekOrigin.Begin);

for (int count = 0; count <= thumbInfo.Length; count++)
bytBMPBuff[count] = binaryReader.ReadByte();

var bmp = new Bitmap(udtHeader.Width, udtHeader.Height);
var lngCnt = 0;

for (var lngY = 1; lngY <= udtHeader.Height; lngY++)
{
for (var lngX = udtHeader.Width; lngX >= 1; lngX--)
{
int lngColor = bytBMPBuff[bytBMPBuff.GetUpperBound(0) - lngCnt];
var udtColor = udtColors[lngColor];

var intRed = Convert.ToInt16(udtColor.Red);
var intGreen = Convert.ToInt16(udtColor.Green);
var intBlue = Convert.ToInt16(udtColor.Blue);
lngColor = ColorTranslator.ToOle(Color.FromArgb(intRed, intGreen, intBlue));

if (!RetainBackgroudColor)
{
if (lngColor == ColorTranslator.ToOle(ColorBlack))
lngColor = ColorTranslator.ToOle(ColorWhite);
else if (lngColor == ColorTranslator.ToOle(ColorWhite))
lngColor = ColorTranslator.ToOle(ColorBlack);
}

bmp.SetPixel(lngX - 1, lngY - 1, ColorTranslator.FromOle(lngColor));
lngCnt++;
}
}

using var outputStream = new MemoryStream();
bmp.Save(outputStream, ImageFormat.Png);
return outputStream.ToArray();
}

case ThumbnailImageCodes.NULL:
return new byte[0];

case ThumbnailInfo.NullType:
break; // DWG file doesn't contain a thumbnail
}
#pragma warning restore CC0120
Expand All @@ -77,18 +135,38 @@ public override byte[] ExtractThumbnailBytes(Stream stream)
return new byte[0];
}

public static byte[] ToByteArray(Image image, ImageFormat format)
private struct ThumbnailInfo
{
public const byte BmpType = 2;
public const byte PngType = 6;
public const byte NullType = 3;

public byte Type;
public int Start;
public int Length;
}

private struct BitmapHeader
{
using var ms = new MemoryStream();
image.Save(ms, format);
return ms.ToArray();
public int Size;
public int Width;
public int Height;
public short Planes;
public short BitCount;
public int Compression;
public int SizeImage;
public int XPelsPerMeter;
public int YPelsPerMeter;
public int ClrUsed;
public int ClrImportant;
}

private struct ThumbnailImageCodes
private struct RgbQuad
{
internal const byte BMP = 2;
internal const byte PNG = 6;
internal const byte NULL = 3;
public byte Blue;
public byte Green;
public byte Red;
public byte Reserved;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,7 @@ namespace CodeCave.Revit.Toolkit.Thumbnails
/// <seealso cref="ThumbnailExtractor" />
public partial class RevitTumbnailExtractor : ThumbnailExtractor
{


/// <inheritdoc/>
public override byte[] ExtractThumbnailBytes(Stream stream)
{
byte[] thumbnailBytes;
Expand Down
Loading

0 comments on commit 42d7236

Please sign in to comment.