Performance improvement

This commit is contained in:
Perfare 2021-12-06 13:36:22 +08:00
parent 88c5804586
commit 80653711cd
13 changed files with 369 additions and 888 deletions

View File

@ -8,4 +8,13 @@
<Copyright>Copyright © Perfare 2018-2021</Copyright> <Copyright>Copyright © Perfare 2018-2021</Copyright>
</PropertyGroup> </PropertyGroup>
<ItemGroup>
<PackageReference Include="K4os.Compression.LZ4" Version="1.2.16" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'net472' ">
<PackageReference Include="System.Memory" Version="4.5.4" />
<PackageReference Include="K4os.Compression.LZ4" Version="1.1.11" />
</ItemGroup>
</Project> </Project>

View File

@ -0,0 +1,10 @@
using System.Buffers;
namespace AssetStudio
{
public static class BigArrayPool<T>
{
private static readonly ArrayPool<T> s_shared = ArrayPool<T>.Create(64 * 1024 * 1024, 3);
public static ArrayPool<T> Shared => s_shared;
}
}

View File

@ -1,8 +1,6 @@
using System; using K4os.Compression.LZ4;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using Lz4;
namespace AssetStudio namespace AssetStudio
{ {
@ -221,30 +219,33 @@ namespace AssetStudio
{ {
blocksInfoBytes = reader.ReadBytes((int)m_Header.compressedBlocksInfoSize); blocksInfoBytes = reader.ReadBytes((int)m_Header.compressedBlocksInfoSize);
} }
var blocksInfoCompressedStream = new MemoryStream(blocksInfoBytes);
MemoryStream blocksInfoUncompresseddStream; MemoryStream blocksInfoUncompresseddStream;
var uncompressedSize = m_Header.uncompressedBlocksInfoSize;
switch (m_Header.flags & 0x3F) //kArchiveCompressionTypeMask switch (m_Header.flags & 0x3F) //kArchiveCompressionTypeMask
{ {
default: //None default: //None
{ {
blocksInfoUncompresseddStream = blocksInfoCompressedStream; blocksInfoUncompresseddStream = new MemoryStream(blocksInfoBytes);
break; break;
} }
case 1: //LZMA case 1: //LZMA
{ {
blocksInfoUncompresseddStream = new MemoryStream((int)(m_Header.uncompressedBlocksInfoSize)); blocksInfoUncompresseddStream = new MemoryStream((int)(uncompressedSize));
using (var blocksInfoCompressedStream = new MemoryStream(blocksInfoBytes))
{
SevenZipHelper.StreamDecompress(blocksInfoCompressedStream, blocksInfoUncompresseddStream, m_Header.compressedBlocksInfoSize, m_Header.uncompressedBlocksInfoSize); SevenZipHelper.StreamDecompress(blocksInfoCompressedStream, blocksInfoUncompresseddStream, m_Header.compressedBlocksInfoSize, m_Header.uncompressedBlocksInfoSize);
}
blocksInfoUncompresseddStream.Position = 0; blocksInfoUncompresseddStream.Position = 0;
blocksInfoCompressedStream.Close();
break; break;
} }
case 2: //LZ4 case 2: //LZ4
case 3: //LZ4HC case 3: //LZ4HC
{ {
var uncompressedBytes = new byte[m_Header.uncompressedBlocksInfoSize]; var uncompressedBytes = new byte[uncompressedSize];
using (var decoder = new Lz4DecoderStream(blocksInfoCompressedStream)) var numWrite = LZ4Codec.Decode(blocksInfoBytes, uncompressedBytes);
if (numWrite != uncompressedSize)
{ {
decoder.Read(uncompressedBytes, 0, uncompressedBytes.Length); throw new IOException($"Lz4 decompression error, write {numWrite} bytes but expected {uncompressedSize} bytes");
} }
blocksInfoUncompresseddStream = new MemoryStream(uncompressedBytes); blocksInfoUncompresseddStream = new MemoryStream(uncompressedBytes);
break; break;
@ -299,11 +300,19 @@ namespace AssetStudio
case 2: //LZ4 case 2: //LZ4
case 3: //LZ4HC case 3: //LZ4HC
{ {
var compressedStream = new MemoryStream(reader.ReadBytes((int)blockInfo.compressedSize)); var compressedSize = (int)blockInfo.compressedSize;
using (var lz4Stream = new Lz4DecoderStream(compressedStream)) var compressedBytes = BigArrayPool<byte>.Shared.Rent(compressedSize);
reader.Read(compressedBytes, 0, compressedSize);
var uncompressedSize = (int)blockInfo.uncompressedSize;
var uncompressedBytes = BigArrayPool<byte>.Shared.Rent(uncompressedSize);
var numWrite = LZ4Codec.Decode(compressedBytes, 0, compressedSize, uncompressedBytes, 0, uncompressedSize);
if (numWrite != uncompressedSize)
{ {
lz4Stream.CopyTo(blocksStream, blockInfo.uncompressedSize); throw new IOException($"Lz4 decompression error, write {numWrite} bytes but expected {uncompressedSize} bytes");
} }
blocksStream.Write(uncompressedBytes, 0, uncompressedSize);
BigArrayPool<byte>.Shared.Return(compressedBytes);
BigArrayPool<byte>.Shared.Return(uncompressedBytes);
break; break;
} }
} }

View File

@ -1,540 +0,0 @@
#define CHECK_ARGS
#define CHECK_EOF
//#define LOCAL_SHADOW
using System;
using System.IO;
namespace Lz4
{
public class Lz4DecoderStream : Stream
{
public Lz4DecoderStream(Stream input, long inputLength = long.MaxValue)
{
Reset(input, inputLength);
}
private void Reset(Stream input, long inputLength = long.MaxValue)
{
this.inputLength = inputLength;
this.input = input;
phase = DecodePhase.ReadToken;
decodeBufferPos = 0;
litLen = 0;
matLen = 0;
matDst = 0;
inBufPos = DecBufLen;
inBufEnd = DecBufLen;
}
protected override void Dispose(bool disposing)
{
try
{
if (disposing && input != null)
{
input.Close();
}
input = null;
decodeBuffer = null;
}
finally
{
base.Dispose(disposing);
}
}
private long inputLength;
private Stream input;
//because we might not be able to match back across invocations,
//we have to keep the last window's worth of bytes around for reuse
//we use a circular buffer for this - every time we write into this
//buffer, we also write the same into our output buffer
private const int DecBufLen = 0x10000;
private const int DecBufMask = 0xFFFF;
private const int InBufLen = 128;
private byte[] decodeBuffer = new byte[DecBufLen + InBufLen];
private int decodeBufferPos, inBufPos, inBufEnd;
//we keep track of which phase we're in so that we can jump right back
//into the correct part of decoding
private DecodePhase phase;
private enum DecodePhase
{
ReadToken,
ReadExLiteralLength,
CopyLiteral,
ReadOffset,
ReadExMatchLength,
CopyMatch,
}
//state within interruptable phases and across phase boundaries is
//kept here - again, so that we can punt out and restart freely
private int litLen, matLen, matDst;
public override int Read(byte[] buffer, int offset, int count)
{
#if CHECK_ARGS
if (buffer == null)
throw new ArgumentNullException("buffer");
if (offset < 0 || count < 0 || buffer.Length - count < offset)
throw new ArgumentOutOfRangeException();
if (input == null)
throw new InvalidOperationException();
#endif
int nRead, nToRead = count;
var decBuf = decodeBuffer;
//the stringy gotos are obnoxious, but their purpose is to
//make it *blindingly* obvious how the state machine transitions
//back and forth as it reads - remember, we can yield out of
//this routine in several places, and we must be able to re-enter
//and pick up where we left off!
#if LOCAL_SHADOW
var phase = this.phase;
var inBufPos = this.inBufPos;
var inBufEnd = this.inBufEnd;
#endif
switch (phase)
{
case DecodePhase.ReadToken:
goto readToken;
case DecodePhase.ReadExLiteralLength:
goto readExLiteralLength;
case DecodePhase.CopyLiteral:
goto copyLiteral;
case DecodePhase.ReadOffset:
goto readOffset;
case DecodePhase.ReadExMatchLength:
goto readExMatchLength;
case DecodePhase.CopyMatch:
goto copyMatch;
}
readToken:
int tok;
if (inBufPos < inBufEnd)
{
tok = decBuf[inBufPos++];
}
else
{
#if LOCAL_SHADOW
this.inBufPos = inBufPos;
#endif
tok = ReadByteCore();
#if LOCAL_SHADOW
inBufPos = this.inBufPos;
inBufEnd = this.inBufEnd;
#endif
#if CHECK_EOF
if (tok == -1)
goto finish;
#endif
}
litLen = tok >> 4;
matLen = (tok & 0xF) + 4;
switch (litLen)
{
case 0:
phase = DecodePhase.ReadOffset;
goto readOffset;
case 0xF:
phase = DecodePhase.ReadExLiteralLength;
goto readExLiteralLength;
default:
phase = DecodePhase.CopyLiteral;
goto copyLiteral;
}
readExLiteralLength:
int exLitLen;
if (inBufPos < inBufEnd)
{
exLitLen = decBuf[inBufPos++];
}
else
{
#if LOCAL_SHADOW
this.inBufPos = inBufPos;
#endif
exLitLen = ReadByteCore();
#if LOCAL_SHADOW
inBufPos = this.inBufPos;
inBufEnd = this.inBufEnd;
#endif
#if CHECK_EOF
if (exLitLen == -1)
goto finish;
#endif
}
litLen += exLitLen;
if (exLitLen == 255)
goto readExLiteralLength;
phase = DecodePhase.CopyLiteral;
goto copyLiteral;
copyLiteral:
int nReadLit = litLen < nToRead ? litLen : nToRead;
if (nReadLit != 0)
{
if (inBufPos + nReadLit <= inBufEnd)
{
int ofs = offset;
for (int c = nReadLit; c-- != 0;)
buffer[ofs++] = decBuf[inBufPos++];
nRead = nReadLit;
}
else
{
#if LOCAL_SHADOW
this.inBufPos = inBufPos;
#endif
nRead = ReadCore(buffer, offset, nReadLit);
#if LOCAL_SHADOW
inBufPos = this.inBufPos;
inBufEnd = this.inBufEnd;
#endif
#if CHECK_EOF
if (nRead == 0)
goto finish;
#endif
}
offset += nRead;
nToRead -= nRead;
litLen -= nRead;
if (litLen != 0)
goto copyLiteral;
}
if (nToRead == 0)
goto finish;
phase = DecodePhase.ReadOffset;
goto readOffset;
readOffset:
if (inBufPos + 1 < inBufEnd)
{
matDst = (decBuf[inBufPos + 1] << 8) | decBuf[inBufPos];
inBufPos += 2;
}
else
{
#if LOCAL_SHADOW
this.inBufPos = inBufPos;
#endif
matDst = ReadOffsetCore();
#if LOCAL_SHADOW
inBufPos = this.inBufPos;
inBufEnd = this.inBufEnd;
#endif
#if CHECK_EOF
if (matDst == -1)
goto finish;
#endif
}
if (matLen == 15 + 4)
{
phase = DecodePhase.ReadExMatchLength;
goto readExMatchLength;
}
else
{
phase = DecodePhase.CopyMatch;
goto copyMatch;
}
readExMatchLength:
int exMatLen;
if (inBufPos < inBufEnd)
{
exMatLen = decBuf[inBufPos++];
}
else
{
#if LOCAL_SHADOW
this.inBufPos = inBufPos;
#endif
exMatLen = ReadByteCore();
#if LOCAL_SHADOW
inBufPos = this.inBufPos;
inBufEnd = this.inBufEnd;
#endif
#if CHECK_EOF
if (exMatLen == -1)
goto finish;
#endif
}
matLen += exMatLen;
if (exMatLen == 255)
goto readExMatchLength;
phase = DecodePhase.CopyMatch;
goto copyMatch;
copyMatch:
int nCpyMat = matLen < nToRead ? matLen : nToRead;
if (nCpyMat != 0)
{
nRead = count - nToRead;
int bufDst = matDst - nRead;
if (bufDst > 0)
{
//offset is fairly far back, we need to pull from the buffer
int bufSrc = decodeBufferPos - bufDst;
if (bufSrc < 0)
bufSrc += DecBufLen;
int bufCnt = bufDst < nCpyMat ? bufDst : nCpyMat;
for (int c = bufCnt; c-- != 0;)
buffer[offset++] = decBuf[bufSrc++ & DecBufMask];
}
else
{
bufDst = 0;
}
int sOfs = offset - matDst;
for (int i = bufDst; i < nCpyMat; i++)
buffer[offset++] = buffer[sOfs++];
nToRead -= nCpyMat;
matLen -= nCpyMat;
}
if (nToRead == 0)
goto finish;
phase = DecodePhase.ReadToken;
goto readToken;
finish:
nRead = count - nToRead;
int nToBuf = nRead < DecBufLen ? nRead : DecBufLen;
int repPos = offset - nToBuf;
if (nToBuf == DecBufLen)
{
Buffer.BlockCopy(buffer, repPos, decBuf, 0, DecBufLen);
decodeBufferPos = 0;
}
else
{
int decPos = decodeBufferPos;
while (nToBuf-- != 0)
decBuf[decPos++ & DecBufMask] = buffer[repPos++];
decodeBufferPos = decPos & DecBufMask;
}
#if LOCAL_SHADOW
this.phase = phase;
this.inBufPos = inBufPos;
#endif
return nRead;
}
private int ReadByteCore()
{
var buf = decodeBuffer;
if (inBufPos == inBufEnd)
{
int nRead = input.Read(buf, DecBufLen,
InBufLen < inputLength ? InBufLen : (int)inputLength);
#if CHECK_EOF
if (nRead == 0)
return -1;
#endif
inputLength -= nRead;
inBufPos = DecBufLen;
inBufEnd = DecBufLen + nRead;
}
return buf[inBufPos++];
}
private int ReadOffsetCore()
{
var buf = decodeBuffer;
if (inBufPos == inBufEnd)
{
int nRead = input.Read(buf, DecBufLen,
InBufLen < inputLength ? InBufLen : (int)inputLength);
#if CHECK_EOF
if (nRead == 0)
return -1;
#endif
inputLength -= nRead;
inBufPos = DecBufLen;
inBufEnd = DecBufLen + nRead;
}
if (inBufEnd - inBufPos == 1)
{
buf[DecBufLen] = buf[inBufPos];
int nRead = input.Read(buf, DecBufLen + 1,
InBufLen - 1 < inputLength ? InBufLen - 1 : (int)inputLength);
#if CHECK_EOF
if (nRead == 0)
{
inBufPos = DecBufLen;
inBufEnd = DecBufLen + 1;
return -1;
}
#endif
inputLength -= nRead;
inBufPos = DecBufLen;
inBufEnd = DecBufLen + nRead + 1;
}
int ret = (buf[inBufPos + 1] << 8) | buf[inBufPos];
inBufPos += 2;
return ret;
}
private int ReadCore(byte[] buffer, int offset, int count)
{
int nToRead = count;
var buf = decodeBuffer;
int inBufLen = inBufEnd - inBufPos;
int fromBuf = nToRead < inBufLen ? nToRead : inBufLen;
if (fromBuf != 0)
{
var bufPos = inBufPos;
for (int c = fromBuf; c-- != 0;)
buffer[offset++] = buf[bufPos++];
inBufPos = bufPos;
nToRead -= fromBuf;
}
if (nToRead != 0)
{
int nRead;
if (nToRead >= InBufLen)
{
nRead = input.Read(buffer, offset,
nToRead < inputLength ? nToRead : (int)inputLength);
nToRead -= nRead;
}
else
{
nRead = input.Read(buf, DecBufLen,
InBufLen < inputLength ? InBufLen : (int)inputLength);
inBufPos = DecBufLen;
inBufEnd = DecBufLen + nRead;
fromBuf = nToRead < nRead ? nToRead : nRead;
var bufPos = inBufPos;
for (int c = fromBuf; c-- != 0;)
buffer[offset++] = buf[bufPos++];
inBufPos = bufPos;
nToRead -= fromBuf;
}
inputLength -= nRead;
}
return count - nToRead;
}
#region Stream internals
public override bool CanRead => true;
public override bool CanSeek => false;
public override bool CanWrite => false;
public override void Flush()
{
}
public override long Length => throw new NotSupportedException();
public override long Position
{
get => throw new NotSupportedException();
set => throw new NotSupportedException();
}
public override long Seek(long offset, SeekOrigin origin)
{
throw new NotSupportedException();
}
public override void SetLength(long value)
{
throw new NotSupportedException();
}
public override void Write(byte[] buffer, int offset, int count)
{
throw new NotSupportedException();
}
#endregion
}
}

View File

@ -11,6 +11,8 @@ namespace AssetStudio
private long size; private long size;
private BinaryReader reader; private BinaryReader reader;
public int Size { get => (int)size; }
public ResourceReader(string path, SerializedFile assetsFile, long offset, long size) public ResourceReader(string path, SerializedFile assetsFile, long offset, long size)
{ {
needSearch = true; needSearch = true;
@ -69,6 +71,13 @@ namespace AssetStudio
return binaryReader.ReadBytes((int)size); return binaryReader.ReadBytes((int)size);
} }
public void GetData(byte[] buff)
{
var binaryReader = GetReader();
binaryReader.BaseStream.Position = offset;
binaryReader.Read(buff, 0, (int)size);
}
public void WriteData(string path) public void WriteData(string path)
{ {
var binaryReader = GetReader(); var binaryReader = GetReader();

View File

@ -6,7 +6,6 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Drawing; using System.Drawing;
using System.Drawing.Imaging;
using System.Drawing.Text; using System.Drawing.Text;
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;
@ -19,8 +18,6 @@ using System.Timers;
using System.Windows.Forms; using System.Windows.Forms;
using static AssetStudioGUI.Studio; using static AssetStudioGUI.Studio;
using Font = AssetStudio.Font; using Font = AssetStudio.Font;
using ImageFormat = AssetStudio.ImageFormat;
using PixelFormat = System.Drawing.Imaging.PixelFormat;
#if NET472 #if NET472
using Vector3 = OpenTK.Vector3; using Vector3 = OpenTK.Vector3;
using Vector4 = OpenTK.Vector4; using Vector4 = OpenTK.Vector4;
@ -35,7 +32,7 @@ namespace AssetStudioGUI
partial class AssetStudioGUIForm : Form partial class AssetStudioGUIForm : Form
{ {
private AssetItem lastSelectedItem; private AssetItem lastSelectedItem;
private Bitmap imageTexture; private DirectBitmap imageTexture;
private string tempClipboard; private string tempClipboard;
private FMOD.System system; private FMOD.System system;
@ -396,7 +393,7 @@ namespace AssetStudioGUI
{ {
if (enablePreview.Checked && imageTexture != null) if (enablePreview.Checked && imageTexture != null)
{ {
previewPanel.BackgroundImage = imageTexture; previewPanel.BackgroundImage = imageTexture.Bitmap;
} }
else else
{ {
@ -759,10 +756,11 @@ namespace AssetStudioGUI
private void PreviewTexture2D(AssetItem assetItem, Texture2D m_Texture2D) private void PreviewTexture2D(AssetItem assetItem, Texture2D m_Texture2D)
{ {
var stream = m_Texture2D.ConvertToStream(ImageFormat.Png, true); var image = m_Texture2D.ConvertToImage(true);
if (stream != null) if (image != null)
{ {
var bitmap = new Bitmap(stream); var bitmap = new DirectBitmap(image.ConvertToBgra32Bytes(), m_Texture2D.m_Width, m_Texture2D.m_Height);
image.Dispose();
assetItem.InfoText = $"Width: {m_Texture2D.m_Width}\nHeight: {m_Texture2D.m_Height}\nFormat: {m_Texture2D.m_TextureFormat}"; assetItem.InfoText = $"Width: {m_Texture2D.m_Width}\nHeight: {m_Texture2D.m_Height}\nFormat: {m_Texture2D.m_TextureFormat}";
switch (m_Texture2D.m_TextureSettings.m_FilterMode) switch (m_Texture2D.m_TextureSettings.m_FilterMode)
{ {
@ -790,13 +788,11 @@ namespace AssetStudioGUI
assetItem.InfoText += "None"; assetItem.InfoText += "None";
if (validChannel != 4) if (validChannel != 4)
{ {
var bmpData = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb); var bytes = bitmap.Bits;
var bytes = new byte[bitmap.Width * bitmap.Height * 4]; for (int i = 0; i < bitmap.Height; i++)
Marshal.Copy(bmpData.Scan0, bytes, 0, bytes.Length);
for (int i = 0; i < bmpData.Height; i++)
{ {
int offset = Math.Abs(bmpData.Stride) * i; int offset = Math.Abs(bitmap.Stride) * i;
for (int j = 0; j < bmpData.Width; j++) for (int j = 0; j < bitmap.Width; j++)
{ {
bytes[offset] = textureChannels[0] ? bytes[offset] : validChannel == 1 && textureChannels[3] ? byte.MaxValue : byte.MinValue; bytes[offset] = textureChannels[0] ? bytes[offset] : validChannel == 1 && textureChannels[3] ? byte.MaxValue : byte.MinValue;
bytes[offset + 1] = textureChannels[1] ? bytes[offset + 1] : validChannel == 1 && textureChannels[3] ? byte.MaxValue : byte.MinValue; bytes[offset + 1] = textureChannels[1] ? bytes[offset + 1] : validChannel == 1 && textureChannels[3] ? byte.MaxValue : byte.MinValue;
@ -805,8 +801,6 @@ namespace AssetStudioGUI
offset += 4; offset += 4;
} }
} }
Marshal.Copy(bytes, 0, bmpData.Scan0, bytes.Length);
bitmap.UnlockBits(bmpData);
} }
PreviewTexture(bitmap); PreviewTexture(bitmap);
@ -1170,10 +1164,11 @@ namespace AssetStudioGUI
private void PreviewSprite(AssetItem assetItem, Sprite m_Sprite) private void PreviewSprite(AssetItem assetItem, Sprite m_Sprite)
{ {
var stream = m_Sprite.GetImage(ImageFormat.Png); var image = m_Sprite.GetImage();
if (stream != null) if (image != null)
{ {
var bitmap = new Bitmap(stream); var bitmap = new DirectBitmap(image.ConvertToBgra32Bytes(), image.Width, image.Height);
image.Dispose();
assetItem.InfoText = $"Width: {bitmap.Width}\nHeight: {bitmap.Height}\n"; assetItem.InfoText = $"Width: {bitmap.Width}\nHeight: {bitmap.Height}\n";
PreviewTexture(bitmap); PreviewTexture(bitmap);
} }
@ -1183,11 +1178,11 @@ namespace AssetStudioGUI
} }
} }
private void PreviewTexture(Bitmap bitmap) private void PreviewTexture(DirectBitmap bitmap)
{ {
imageTexture?.Dispose(); imageTexture?.Dispose();
imageTexture = bitmap; imageTexture = bitmap;
previewPanel.BackgroundImage = imageTexture; previewPanel.BackgroundImage = imageTexture.Bitmap;
if (imageTexture.Width > previewPanel.Width || imageTexture.Height > previewPanel.Height) if (imageTexture.Width > previewPanel.Width || imageTexture.Height > previewPanel.Height)
previewPanel.BackgroundImageLayout = ImageLayout.Zoom; previewPanel.BackgroundImageLayout = ImageLayout.Zoom;
else else
@ -1238,6 +1233,7 @@ namespace AssetStudioGUI
classesListView.Groups.Clear(); classesListView.Groups.Clear();
previewPanel.BackgroundImage = Properties.Resources.preview; previewPanel.BackgroundImage = Properties.Resources.preview;
imageTexture?.Dispose(); imageTexture?.Dispose();
imageTexture = null;
previewPanel.BackgroundImageLayout = ImageLayout.Center; previewPanel.BackgroundImageLayout = ImageLayout.Center;
assetInfoLabel.Visible = false; assetInfoLabel.Visible = false;
assetInfoLabel.Text = null; assetInfoLabel.Text = null;

View File

@ -0,0 +1,43 @@
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
namespace AssetStudioGUI
{
public sealed class DirectBitmap : IDisposable
{
public DirectBitmap(byte[] buff, int width, int height)
{
Width = width;
Height = height;
Bits = buff;
m_handle = GCHandle.Alloc(Bits, GCHandleType.Pinned);
m_bitmap = new Bitmap(Width, Height, Stride, PixelFormat.Format32bppArgb, m_handle.AddrOfPinnedObject());
}
private void Dispose(bool disposing)
{
if (disposing)
{
m_bitmap.Dispose();
m_handle.Free();
}
m_bitmap = null;
}
public void Dispose()
{
Dispose(true);
}
public int Height { get; }
public int Width { get; }
public int Stride => Width * 4;
public byte[] Bits { get; }
public Bitmap Bitmap => m_bitmap;
private Bitmap m_bitmap;
private readonly GCHandle m_handle;
}
}

View File

@ -17,12 +17,15 @@ namespace AssetStudioGUI
var type = Properties.Settings.Default.convertType; var type = Properties.Settings.Default.convertType;
if (!TryExportFile(exportPath, item, "." + type.ToString().ToLower(), out var exportFullPath)) if (!TryExportFile(exportPath, item, "." + type.ToString().ToLower(), out var exportFullPath))
return false; return false;
var stream = m_Texture2D.ConvertToStream(type, true); var image = m_Texture2D.ConvertToImage(true);
if (stream == null) if (image == null)
return false; return false;
using (stream) using (image)
{ {
File.WriteAllBytes(exportFullPath, stream.ToArray()); using (var file = File.OpenWrite(exportFullPath))
{
image.WriteToStream(file, type);
}
return true; return true;
} }
} }
@ -229,12 +232,15 @@ namespace AssetStudioGUI
var type = Properties.Settings.Default.convertType; var type = Properties.Settings.Default.convertType;
if (!TryExportFile(exportPath, item, "." + type.ToString().ToLower(), out var exportFullPath)) if (!TryExportFile(exportPath, item, "." + type.ToString().ToLower(), out var exportFullPath))
return false; return false;
var stream = ((Sprite)item.Asset).GetImage(type); var image = ((Sprite)item.Asset).GetImage();
if (stream != null) if (image != null)
{ {
using (stream) using (image)
{ {
File.WriteAllBytes(exportFullPath, stream.ToArray()); using (var file = File.OpenWrite(exportFullPath))
{
image.WriteToStream(file, type);
}
return true; return true;
} }
} }

View File

@ -1,39 +1,55 @@
using SixLabors.ImageSharp; using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Formats.Bmp; using SixLabors.ImageSharp.Formats.Bmp;
using SixLabors.ImageSharp.Formats.Tga; using SixLabors.ImageSharp.Formats.Tga;
using SixLabors.ImageSharp.PixelFormats;
using System.IO; using System.IO;
using System.Runtime.InteropServices;
namespace AssetStudio namespace AssetStudio
{ {
public static class ImageExtensions public static class ImageExtensions
{ {
public static MemoryStream ConvertToStream(this Image image, ImageFormat imageFormat) public static void WriteToStream(this Image image, Stream stream, ImageFormat imageFormat)
{ {
var outputStream = new MemoryStream();
switch (imageFormat) switch (imageFormat)
{ {
case ImageFormat.Jpeg: case ImageFormat.Jpeg:
image.SaveAsJpeg(outputStream); image.SaveAsJpeg(stream);
break; break;
case ImageFormat.Png: case ImageFormat.Png:
image.SaveAsPng(outputStream); image.SaveAsPng(stream);
break; break;
case ImageFormat.Bmp: case ImageFormat.Bmp:
image.Save(outputStream, new BmpEncoder image.Save(stream, new BmpEncoder
{ {
BitsPerPixel = BmpBitsPerPixel.Pixel32, BitsPerPixel = BmpBitsPerPixel.Pixel32,
SupportTransparency = true SupportTransparency = true
}); });
break; break;
case ImageFormat.Tga: case ImageFormat.Tga:
image.Save(outputStream, new TgaEncoder image.Save(stream, new TgaEncoder
{ {
BitsPerPixel = TgaBitsPerPixel.Pixel32, BitsPerPixel = TgaBitsPerPixel.Pixel32,
Compression = TgaCompression.None Compression = TgaCompression.None
}); });
break; break;
} }
return outputStream; }
public static MemoryStream ConvertToStream(this Image image, ImageFormat imageFormat)
{
var stream = new MemoryStream();
image.WriteToStream(stream, imageFormat);
return stream;
}
public static byte[] ConvertToBgra32Bytes(this Image<Bgra32> image)
{
if (image.TryGetSinglePixelSpan(out var pixelSpan))
{
return MemoryMarshal.AsBytes(pixelSpan).ToArray();
}
return null;
} }
} }
} }

View File

@ -1,10 +1,10 @@
using System; using K4os.Compression.LZ4;
using System;
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using Lz4;
namespace AssetStudio namespace AssetStudio
{ {
@ -15,10 +15,7 @@ namespace AssetStudio
if (shader.m_SubProgramBlob != null) //5.3 - 5.4 if (shader.m_SubProgramBlob != null) //5.3 - 5.4
{ {
var decompressedBytes = new byte[shader.decompressedSize]; var decompressedBytes = new byte[shader.decompressedSize];
using (var decoder = new Lz4DecoderStream(new MemoryStream(shader.m_SubProgramBlob))) LZ4Codec.Decode(shader.m_SubProgramBlob, decompressedBytes);
{
decoder.Read(decompressedBytes, 0, (int)shader.decompressedSize);
}
using (var blobReader = new BinaryReader(new MemoryStream(decompressedBytes))) using (var blobReader = new BinaryReader(new MemoryStream(decompressedBytes)))
{ {
var program = new ShaderProgram(blobReader, shader.version); var program = new ShaderProgram(blobReader, shader.version);
@ -42,10 +39,7 @@ namespace AssetStudio
var compressedBytes = new byte[shader.compressedLengths[i]]; var compressedBytes = new byte[shader.compressedLengths[i]];
Buffer.BlockCopy(shader.compressedBlob, (int)shader.offsets[i], compressedBytes, 0, (int)shader.compressedLengths[i]); Buffer.BlockCopy(shader.compressedBlob, (int)shader.offsets[i], compressedBytes, 0, (int)shader.compressedLengths[i]);
var decompressedBytes = new byte[shader.decompressedLengths[i]]; var decompressedBytes = new byte[shader.decompressedLengths[i]];
using (var decoder = new Lz4DecoderStream(new MemoryStream(compressedBytes))) LZ4Codec.Decode(compressedBytes, decompressedBytes);
{
decoder.Read(decompressedBytes, 0, (int)shader.decompressedLengths[i]);
}
using (var blobReader = new BinaryReader(new MemoryStream(decompressedBytes))) using (var blobReader = new BinaryReader(new MemoryStream(decompressedBytes)))
{ {
shaderPrograms[i] = new ShaderProgram(blobReader, shader.version); shaderPrograms[i] = new ShaderProgram(blobReader, shader.version);

View File

@ -13,20 +13,7 @@ namespace AssetStudio
{ {
public static class SpriteHelper public static class SpriteHelper
{ {
public static MemoryStream GetImage(this Sprite m_Sprite, ImageFormat imageFormat) public static Image<Bgra32> GetImage(this Sprite m_Sprite)
{
var image = GetImage(m_Sprite);
if (image != null)
{
using (image)
{
return image.ConvertToStream(imageFormat);
}
}
return null;
}
public static Image GetImage(this Sprite m_Sprite)
{ {
if (m_Sprite.m_SpriteAtlas != null && m_Sprite.m_SpriteAtlas.TryGet(out var m_SpriteAtlas)) if (m_Sprite.m_SpriteAtlas != null && m_Sprite.m_SpriteAtlas.TryGet(out var m_SpriteAtlas))
{ {
@ -45,7 +32,7 @@ namespace AssetStudio
return null; return null;
} }
private static Image CutImage(Texture2D m_Texture2D, Sprite m_Sprite, Rectf textureRect, Vector2 textureRectOffset, SpriteSettings settingsRaw) private static Image<Bgra32> CutImage(Texture2D m_Texture2D, Sprite m_Sprite, Rectf textureRect, Vector2 textureRectOffset, SpriteSettings settingsRaw)
{ {
var originalImage = m_Texture2D.ConvertToImage(false); var originalImage = m_Texture2D.ConvertToImage(false);
if (originalImage != null) if (originalImage != null)
@ -100,7 +87,7 @@ namespace AssetStudio
{ {
GraphicsOptions = graphicsOptions GraphicsOptions = graphicsOptions
}; };
using (var mask = new Image<Argb32>(rect.Width, rect.Height, SixLabors.ImageSharp.Color.Black)) using (var mask = new Image<Bgra32>(rect.Width, rect.Height, SixLabors.ImageSharp.Color.Black))
{ {
mask.Mutate(x => x.Fill(options, SixLabors.ImageSharp.Color.Red, path)); mask.Mutate(x => x.Fill(options, SixLabors.ImageSharp.Color.Red, path));
var bursh = new ImageBrush(mask); var bursh = new ImageBrush(mask);

View File

@ -1,23 +1,20 @@
using System; using System;
using System.Linq;
using Texture2DDecoder; using Texture2DDecoder;
namespace AssetStudio namespace AssetStudio
{ {
public class Texture2DConverter public class Texture2DConverter
{ {
private ResourceReader reader;
private int m_Width; private int m_Width;
private int m_Height; private int m_Height;
private TextureFormat m_TextureFormat; private TextureFormat m_TextureFormat;
private int image_data_size;
private byte[] image_data;
private int[] version; private int[] version;
private BuildTarget platform; private BuildTarget platform;
public Texture2DConverter(Texture2D m_Texture2D) public Texture2DConverter(Texture2D m_Texture2D)
{ {
image_data = m_Texture2D.image_data.GetData(); reader = m_Texture2D.image_data;
image_data_size = image_data.Length;
m_Width = m_Texture2D.m_Width; m_Width = m_Texture2D.m_Width;
m_Height = m_Texture2D.m_Height; m_Height = m_Texture2D.m_Height;
m_TextureFormat = m_Texture2D.m_TextureFormat; m_TextureFormat = m_Texture2D.m_TextureFormat;
@ -25,193 +22,188 @@ namespace AssetStudio
platform = m_Texture2D.platform; platform = m_Texture2D.platform;
} }
public byte[] DecodeTexture2D() public bool DecodeTexture2D(byte[] bytes)
{ {
byte[] bytes = null; if (reader.Size == 0 || m_Width == 0 || m_Height == 0)
{
return false;
}
var flag = false;
var buff = BigArrayPool<byte>.Shared.Rent(reader.Size);
reader.GetData(buff);
switch (m_TextureFormat) switch (m_TextureFormat)
{ {
case TextureFormat.Alpha8: //test pass case TextureFormat.Alpha8: //test pass
bytes = DecodeAlpha8(); flag = DecodeAlpha8(buff, bytes);
break; break;
case TextureFormat.ARGB4444: //test pass case TextureFormat.ARGB4444: //test pass
SwapBytesForXbox(); SwapBytesForXbox(buff);
bytes = DecodeARGB4444(); flag = DecodeARGB4444(buff, bytes);
break; break;
case TextureFormat.RGB24: //test pass case TextureFormat.RGB24: //test pass
bytes = DecodeRGB24(); flag = DecodeRGB24(buff, bytes);
break; break;
case TextureFormat.RGBA32: //test pass case TextureFormat.RGBA32: //test pass
bytes = DecodeRGBA32(); flag = DecodeRGBA32(buff, bytes);
break; break;
case TextureFormat.ARGB32: //test pass case TextureFormat.ARGB32: //test pass
bytes = DecodeARGB32(); flag = DecodeARGB32(buff, bytes);
break; break;
case TextureFormat.RGB565: //test pass case TextureFormat.RGB565: //test pass
SwapBytesForXbox(); SwapBytesForXbox(buff);
bytes = DecodeRGB565(); flag = DecodeRGB565(buff, bytes);
break; break;
case TextureFormat.R16: //test pass case TextureFormat.R16: //test pass
bytes = DecodeR16(); flag = DecodeR16(buff, bytes);
break; break;
case TextureFormat.DXT1: //test pass case TextureFormat.DXT1: //test pass
SwapBytesForXbox(); SwapBytesForXbox(buff);
bytes = DecodeDXT1(); flag = DecodeDXT1(buff, bytes);
break; break;
case TextureFormat.DXT5: //test pass case TextureFormat.DXT5: //test pass
SwapBytesForXbox(); SwapBytesForXbox(buff);
bytes = DecodeDXT5(); flag = DecodeDXT5(buff, bytes);
break; break;
case TextureFormat.RGBA4444: //test pass case TextureFormat.RGBA4444: //test pass
bytes = DecodeRGBA4444(); flag = DecodeRGBA4444(buff, bytes);
break; break;
case TextureFormat.BGRA32: //test pass case TextureFormat.BGRA32: //test pass
bytes = DecodeBGRA32(); flag = DecodeBGRA32(buff, bytes);
break; break;
case TextureFormat.RHalf: case TextureFormat.RHalf:
bytes = DecodeRHalf(); flag = DecodeRHalf(buff, bytes);
break; break;
case TextureFormat.RGHalf: case TextureFormat.RGHalf:
bytes = DecodeRGHalf(); flag = DecodeRGHalf(buff, bytes);
break; break;
case TextureFormat.RGBAHalf: //test pass case TextureFormat.RGBAHalf: //test pass
bytes = DecodeRGBAHalf(); flag = DecodeRGBAHalf(buff, bytes);
break; break;
case TextureFormat.RFloat: case TextureFormat.RFloat:
bytes = DecodeRFloat(); flag = DecodeRFloat(buff, bytes);
break; break;
case TextureFormat.RGFloat: case TextureFormat.RGFloat:
bytes = DecodeRGFloat(); flag = DecodeRGFloat(buff, bytes);
break; break;
case TextureFormat.RGBAFloat: case TextureFormat.RGBAFloat:
bytes = DecodeRGBAFloat(); flag = DecodeRGBAFloat(buff, bytes);
break; break;
case TextureFormat.YUY2: //test pass case TextureFormat.YUY2: //test pass
bytes = DecodeYUY2(); flag = DecodeYUY2(buff, bytes);
break; break;
case TextureFormat.RGB9e5Float: //test pass case TextureFormat.RGB9e5Float: //test pass
bytes = DecodeRGB9e5Float(); flag = DecodeRGB9e5Float(buff, bytes);
break; break;
case TextureFormat.BC4: //test pass case TextureFormat.BC4: //test pass
bytes = DecodeBC4(); flag = DecodeBC4(buff, bytes);
break; break;
case TextureFormat.BC5: //test pass case TextureFormat.BC5: //test pass
bytes = DecodeBC5(); flag = DecodeBC5(buff, bytes);
break; break;
case TextureFormat.BC6H: //test pass case TextureFormat.BC6H: //test pass
bytes = DecodeBC6H(); flag = DecodeBC6H(buff, bytes);
break; break;
case TextureFormat.BC7: //test pass case TextureFormat.BC7: //test pass
bytes = DecodeBC7(); flag = DecodeBC7(buff, bytes);
break; break;
case TextureFormat.DXT1Crunched: //test pass case TextureFormat.DXT1Crunched: //test pass
if (UnpackCrunch()) flag = DecodeDXT1Crunched(buff, bytes);
{
bytes = DecodeDXT1();
}
break; break;
case TextureFormat.DXT5Crunched: //test pass case TextureFormat.DXT5Crunched: //test pass
if (UnpackCrunch()) flag = DecodeDXT5Crunched(buff, bytes);
{
bytes = DecodeDXT5();
}
break; break;
case TextureFormat.PVRTC_RGB2: //test pass case TextureFormat.PVRTC_RGB2: //test pass
case TextureFormat.PVRTC_RGBA2: //test pass case TextureFormat.PVRTC_RGBA2: //test pass
bytes = DecodePVRTC(true); flag = DecodePVRTC(buff, bytes, true);
break; break;
case TextureFormat.PVRTC_RGB4: //test pass case TextureFormat.PVRTC_RGB4: //test pass
case TextureFormat.PVRTC_RGBA4: //test pass case TextureFormat.PVRTC_RGBA4: //test pass
bytes = DecodePVRTC(false); flag = DecodePVRTC(buff, bytes, false);
break; break;
case TextureFormat.ETC_RGB4: //test pass case TextureFormat.ETC_RGB4: //test pass
case TextureFormat.ETC_RGB4_3DS: case TextureFormat.ETC_RGB4_3DS:
bytes = DecodeETC1(); flag = DecodeETC1(buff, bytes);
break; break;
case TextureFormat.ATC_RGB4: //test pass case TextureFormat.ATC_RGB4: //test pass
bytes = DecodeATCRGB4(); flag = DecodeATCRGB4(buff, bytes);
break; break;
case TextureFormat.ATC_RGBA8: //test pass case TextureFormat.ATC_RGBA8: //test pass
bytes = DecodeATCRGBA8(); flag = DecodeATCRGBA8(buff, bytes);
break; break;
case TextureFormat.EAC_R: //test pass case TextureFormat.EAC_R: //test pass
bytes = DecodeEACR(); flag = DecodeEACR(buff, bytes);
break; break;
case TextureFormat.EAC_R_SIGNED: case TextureFormat.EAC_R_SIGNED:
bytes = DecodeEACRSigned(); flag = DecodeEACRSigned(buff, bytes);
break; break;
case TextureFormat.EAC_RG: //test pass case TextureFormat.EAC_RG: //test pass
bytes = DecodeEACRG(); flag = DecodeEACRG(buff, bytes);
break; break;
case TextureFormat.EAC_RG_SIGNED: case TextureFormat.EAC_RG_SIGNED:
bytes = DecodeEACRGSigned(); flag = DecodeEACRGSigned(buff, bytes);
break; break;
case TextureFormat.ETC2_RGB: //test pass case TextureFormat.ETC2_RGB: //test pass
bytes = DecodeETC2(); flag = DecodeETC2(buff, bytes);
break; break;
case TextureFormat.ETC2_RGBA1: //test pass case TextureFormat.ETC2_RGBA1: //test pass
bytes = DecodeETC2A1(); flag = DecodeETC2A1(buff, bytes);
break; break;
case TextureFormat.ETC2_RGBA8: //test pass case TextureFormat.ETC2_RGBA8: //test pass
case TextureFormat.ETC_RGBA8_3DS: case TextureFormat.ETC_RGBA8_3DS:
bytes = DecodeETC2A8(); flag = DecodeETC2A8(buff, bytes);
break; break;
case TextureFormat.ASTC_RGB_4x4: //test pass case TextureFormat.ASTC_RGB_4x4: //test pass
case TextureFormat.ASTC_RGBA_4x4: //test pass case TextureFormat.ASTC_RGBA_4x4: //test pass
case TextureFormat.ASTC_HDR_4x4: //test pass case TextureFormat.ASTC_HDR_4x4: //test pass
bytes = DecodeASTC(4); flag = DecodeASTC(buff, bytes, 4);
break; break;
case TextureFormat.ASTC_RGB_5x5: //test pass case TextureFormat.ASTC_RGB_5x5: //test pass
case TextureFormat.ASTC_RGBA_5x5: //test pass case TextureFormat.ASTC_RGBA_5x5: //test pass
case TextureFormat.ASTC_HDR_5x5: //test pass case TextureFormat.ASTC_HDR_5x5: //test pass
bytes = DecodeASTC(5); flag = DecodeASTC(buff, bytes, 5);
break; break;
case TextureFormat.ASTC_RGB_6x6: //test pass case TextureFormat.ASTC_RGB_6x6: //test pass
case TextureFormat.ASTC_RGBA_6x6: //test pass case TextureFormat.ASTC_RGBA_6x6: //test pass
case TextureFormat.ASTC_HDR_6x6: //test pass case TextureFormat.ASTC_HDR_6x6: //test pass
bytes = DecodeASTC(6); flag = DecodeASTC(buff, bytes, 6);
break; break;
case TextureFormat.ASTC_RGB_8x8: //test pass case TextureFormat.ASTC_RGB_8x8: //test pass
case TextureFormat.ASTC_RGBA_8x8: //test pass case TextureFormat.ASTC_RGBA_8x8: //test pass
case TextureFormat.ASTC_HDR_8x8: //test pass case TextureFormat.ASTC_HDR_8x8: //test pass
bytes = DecodeASTC(8); flag = DecodeASTC(buff, bytes, 8);
break; break;
case TextureFormat.ASTC_RGB_10x10: //test pass case TextureFormat.ASTC_RGB_10x10: //test pass
case TextureFormat.ASTC_RGBA_10x10: //test pass case TextureFormat.ASTC_RGBA_10x10: //test pass
case TextureFormat.ASTC_HDR_10x10: //test pass case TextureFormat.ASTC_HDR_10x10: //test pass
bytes = DecodeASTC(10); flag = DecodeASTC(buff, bytes, 10);
break; break;
case TextureFormat.ASTC_RGB_12x12: //test pass case TextureFormat.ASTC_RGB_12x12: //test pass
case TextureFormat.ASTC_RGBA_12x12: //test pass case TextureFormat.ASTC_RGBA_12x12: //test pass
case TextureFormat.ASTC_HDR_12x12: //test pass case TextureFormat.ASTC_HDR_12x12: //test pass
bytes = DecodeASTC(12); flag = DecodeASTC(buff, bytes, 12);
break; break;
case TextureFormat.RG16: //test pass case TextureFormat.RG16: //test pass
bytes = DecodeRG16(); flag = DecodeRG16(buff, bytes);
break; break;
case TextureFormat.R8: //test pass case TextureFormat.R8: //test pass
bytes = DecodeR8(); flag = DecodeR8(buff, bytes);
break; break;
case TextureFormat.ETC_RGB4Crunched: //test pass case TextureFormat.ETC_RGB4Crunched: //test pass
if (UnpackCrunch()) flag = DecodeETC1Crunched(buff, bytes);
{
bytes = DecodeETC1();
}
break; break;
case TextureFormat.ETC2_RGBA8Crunched: //test pass case TextureFormat.ETC2_RGBA8Crunched: //test pass
if (UnpackCrunch()) flag = DecodeETC2A8Crunched(buff, bytes);
{
bytes = DecodeETC2A8();
}
break; break;
} }
return bytes; BigArrayPool<byte>.Shared.Return(buff);
return flag;
} }
private void SwapBytesForXbox() private void SwapBytesForXbox(byte[] image_data)
{ {
if (platform == BuildTarget.XBOX360) if (platform == BuildTarget.XBOX360)
{ {
for (var i = 0; i < image_data_size / 2; i++) for (var i = 0; i < image_data.Length / 2; i++)
{ {
var b = image_data[i * 2]; var b = image_data[i * 2];
image_data[i * 2] = image_data[i * 2 + 1]; image_data[i * 2] = image_data[i * 2 + 1];
@ -220,22 +212,22 @@ namespace AssetStudio
} }
} }
private byte[] DecodeAlpha8() private bool DecodeAlpha8(byte[] image_data, byte[] buff)
{ {
var buff = Enumerable.Repeat<byte>(0xFF, m_Width * m_Height * 4).ToArray(); var span = new Span<byte>(buff);
span.Fill(0xFF);
for (var i = 0; i < m_Width * m_Height; i++) for (var i = 0; i < m_Width * m_Height; i++)
{ {
buff[i * 4 + 3] = image_data[i]; buff[i * 4 + 3] = image_data[i];
} }
return buff; return true;
} }
private byte[] DecodeARGB4444() private bool DecodeARGB4444(byte[] image_data, byte[] buff)
{
var buff = new byte[m_Width * m_Height * 4];
for (var i = 0; i < m_Width * m_Height; i++)
{ {
var pixelNew = new byte[4]; var pixelNew = new byte[4];
for (var i = 0; i < m_Width * m_Height; i++)
{
var pixelOldShort = BitConverter.ToUInt16(image_data, i * 2); var pixelOldShort = BitConverter.ToUInt16(image_data, i * 2);
pixelNew[0] = (byte)(pixelOldShort & 0x000f); pixelNew[0] = (byte)(pixelOldShort & 0x000f);
pixelNew[1] = (byte)((pixelOldShort & 0x00f0) >> 4); pixelNew[1] = (byte)((pixelOldShort & 0x00f0) >> 4);
@ -245,12 +237,11 @@ namespace AssetStudio
pixelNew[j] = (byte)((pixelNew[j] << 4) | pixelNew[j]); pixelNew[j] = (byte)((pixelNew[j] << 4) | pixelNew[j]);
pixelNew.CopyTo(buff, i * 4); pixelNew.CopyTo(buff, i * 4);
} }
return buff; return true;
} }
private byte[] DecodeRGB24() private bool DecodeRGB24(byte[] image_data, byte[] buff)
{ {
var buff = new byte[m_Width * m_Height * 4];
for (var i = 0; i < m_Width * m_Height; i++) for (var i = 0; i < m_Width * m_Height; i++)
{ {
buff[i * 4] = image_data[i * 3 + 2]; buff[i * 4] = image_data[i * 3 + 2];
@ -258,12 +249,11 @@ namespace AssetStudio
buff[i * 4 + 2] = image_data[i * 3 + 0]; buff[i * 4 + 2] = image_data[i * 3 + 0];
buff[i * 4 + 3] = 255; buff[i * 4 + 3] = 255;
} }
return buff; return true;
} }
private byte[] DecodeRGBA32() private bool DecodeRGBA32(byte[] image_data, byte[] buff)
{ {
var buff = new byte[m_Width * m_Height * 4];
for (var i = 0; i < buff.Length; i += 4) for (var i = 0; i < buff.Length; i += 4)
{ {
buff[i] = image_data[i + 2]; buff[i] = image_data[i + 2];
@ -271,12 +261,11 @@ namespace AssetStudio
buff[i + 2] = image_data[i + 0]; buff[i + 2] = image_data[i + 0];
buff[i + 3] = image_data[i + 3]; buff[i + 3] = image_data[i + 3];
} }
return buff; return true;
} }
private byte[] DecodeARGB32() private bool DecodeARGB32(byte[] image_data, byte[] buff)
{ {
var buff = new byte[m_Width * m_Height * 4];
for (var i = 0; i < buff.Length; i += 4) for (var i = 0; i < buff.Length; i += 4)
{ {
buff[i] = image_data[i + 3]; buff[i] = image_data[i + 3];
@ -284,12 +273,11 @@ namespace AssetStudio
buff[i + 2] = image_data[i + 1]; buff[i + 2] = image_data[i + 1];
buff[i + 3] = image_data[i + 0]; buff[i + 3] = image_data[i + 0];
} }
return buff; return true;
} }
private byte[] DecodeRGB565() private bool DecodeRGB565(byte[] image_data, byte[] buff)
{ {
var buff = new byte[m_Width * m_Height * 4];
for (var i = 0; i < m_Width * m_Height; i++) for (var i = 0; i < m_Width * m_Height; i++)
{ {
var p = BitConverter.ToUInt16(image_data, i * 2); var p = BitConverter.ToUInt16(image_data, i * 2);
@ -298,46 +286,36 @@ namespace AssetStudio
buff[i * 4 + 2] = (byte)((p >> 8 & 0xf8) | (p >> 13)); buff[i * 4 + 2] = (byte)((p >> 8 & 0xf8) | (p >> 13));
buff[i * 4 + 3] = 255; buff[i * 4 + 3] = 255;
} }
return buff; return true;
} }
private byte[] DecodeR16() private bool DecodeR16(byte[] image_data, byte[] buff)
{ {
var buff = new byte[m_Width * m_Height * 4];
for (var i = 0; i < m_Width * m_Height; i++) for (var i = 0; i < m_Width * m_Height; i++)
{ {
buff[i * 4] = 0; //b
buff[i * 4 + 1] = 0; //g
buff[i * 4 + 2] = image_data[i * 2 + 1]; //r buff[i * 4 + 2] = image_data[i * 2 + 1]; //r
buff[i * 4 + 3] = 255; //a buff[i * 4 + 3] = 255; //a
} }
return buff; return true;
} }
private byte[] DecodeDXT1() private bool DecodeDXT1(byte[] image_data, byte[] buff)
{ {
var buff = new byte[m_Width * m_Height * 4]; return TextureDecoder.DecodeDXT1(image_data, m_Width, m_Height, buff);
if (!TextureDecoder.DecodeDXT1(image_data, m_Width, m_Height, buff))
{
return null;
}
return buff;
} }
private byte[] DecodeDXT5() private bool DecodeDXT5(byte[] image_data, byte[] buff)
{ {
var buff = new byte[m_Width * m_Height * 4]; return TextureDecoder.DecodeDXT5(image_data, m_Width, m_Height, buff);
if (!TextureDecoder.DecodeDXT5(image_data, m_Width, m_Height, buff))
{
return null;
}
return buff;
} }
private byte[] DecodeRGBA4444() private bool DecodeRGBA4444(byte[] image_data, byte[] buff)
{
var buff = new byte[m_Width * m_Height * 4];
for (var i = 0; i < m_Width * m_Height; i++)
{ {
var pixelNew = new byte[4]; var pixelNew = new byte[4];
for (var i = 0; i < m_Width * m_Height; i++)
{
var pixelOldShort = BitConverter.ToUInt16(image_data, i * 2); var pixelOldShort = BitConverter.ToUInt16(image_data, i * 2);
pixelNew[0] = (byte)((pixelOldShort & 0x00f0) >> 4); pixelNew[0] = (byte)((pixelOldShort & 0x00f0) >> 4);
pixelNew[1] = (byte)((pixelOldShort & 0x0f00) >> 8); pixelNew[1] = (byte)((pixelOldShort & 0x0f00) >> 8);
@ -347,12 +325,11 @@ namespace AssetStudio
pixelNew[j] = (byte)((pixelNew[j] << 4) | pixelNew[j]); pixelNew[j] = (byte)((pixelNew[j] << 4) | pixelNew[j]);
pixelNew.CopyTo(buff, i * 4); pixelNew.CopyTo(buff, i * 4);
} }
return buff; return true;
} }
private byte[] DecodeBGRA32() private bool DecodeBGRA32(byte[] image_data, byte[] buff)
{ {
var buff = new byte[m_Width * m_Height * 4];
for (var i = 0; i < buff.Length; i += 4) for (var i = 0; i < buff.Length; i += 4)
{ {
buff[i] = image_data[i]; buff[i] = image_data[i];
@ -360,12 +337,11 @@ namespace AssetStudio
buff[i + 2] = image_data[i + 2]; buff[i + 2] = image_data[i + 2];
buff[i + 3] = image_data[i + 3]; buff[i + 3] = image_data[i + 3];
} }
return buff; return true;
} }
private byte[] DecodeRHalf() private bool DecodeRHalf(byte[] image_data, byte[] buff)
{ {
var buff = new byte[m_Width * m_Height * 4];
for (var i = 0; i < buff.Length; i += 4) for (var i = 0; i < buff.Length; i += 4)
{ {
buff[i] = 0; buff[i] = 0;
@ -373,12 +349,11 @@ namespace AssetStudio
buff[i + 2] = (byte)Math.Round(Half.ToHalf(image_data, i / 2) * 255f); buff[i + 2] = (byte)Math.Round(Half.ToHalf(image_data, i / 2) * 255f);
buff[i + 3] = 255; buff[i + 3] = 255;
} }
return buff; return true;
} }
private byte[] DecodeRGHalf() private bool DecodeRGHalf(byte[] image_data, byte[] buff)
{ {
var buff = new byte[m_Width * m_Height * 4];
for (var i = 0; i < buff.Length; i += 4) for (var i = 0; i < buff.Length; i += 4)
{ {
buff[i] = 0; buff[i] = 0;
@ -386,12 +361,11 @@ namespace AssetStudio
buff[i + 2] = (byte)Math.Round(Half.ToHalf(image_data, i) * 255f); buff[i + 2] = (byte)Math.Round(Half.ToHalf(image_data, i) * 255f);
buff[i + 3] = 255; buff[i + 3] = 255;
} }
return buff; return true;
} }
private byte[] DecodeRGBAHalf() private bool DecodeRGBAHalf(byte[] image_data, byte[] buff)
{ {
var buff = new byte[m_Width * m_Height * 4];
for (var i = 0; i < buff.Length; i += 4) for (var i = 0; i < buff.Length; i += 4)
{ {
buff[i] = (byte)Math.Round(Half.ToHalf(image_data, i * 2 + 4) * 255f); buff[i] = (byte)Math.Round(Half.ToHalf(image_data, i * 2 + 4) * 255f);
@ -399,12 +373,11 @@ namespace AssetStudio
buff[i + 2] = (byte)Math.Round(Half.ToHalf(image_data, i * 2) * 255f); buff[i + 2] = (byte)Math.Round(Half.ToHalf(image_data, i * 2) * 255f);
buff[i + 3] = (byte)Math.Round(Half.ToHalf(image_data, i * 2 + 6) * 255f); buff[i + 3] = (byte)Math.Round(Half.ToHalf(image_data, i * 2 + 6) * 255f);
} }
return buff; return true;
} }
private byte[] DecodeRFloat() private bool DecodeRFloat(byte[] image_data, byte[] buff)
{ {
var buff = new byte[m_Width * m_Height * 4];
for (var i = 0; i < buff.Length; i += 4) for (var i = 0; i < buff.Length; i += 4)
{ {
buff[i] = 0; buff[i] = 0;
@ -412,12 +385,11 @@ namespace AssetStudio
buff[i + 2] = (byte)Math.Round(BitConverter.ToSingle(image_data, i) * 255f); buff[i + 2] = (byte)Math.Round(BitConverter.ToSingle(image_data, i) * 255f);
buff[i + 3] = 255; buff[i + 3] = 255;
} }
return buff; return true;
} }
private byte[] DecodeRGFloat() private bool DecodeRGFloat(byte[] image_data, byte[] buff)
{ {
var buff = new byte[m_Width * m_Height * 4];
for (var i = 0; i < buff.Length; i += 4) for (var i = 0; i < buff.Length; i += 4)
{ {
buff[i] = 0; buff[i] = 0;
@ -425,12 +397,11 @@ namespace AssetStudio
buff[i + 2] = (byte)Math.Round(BitConverter.ToSingle(image_data, i * 2) * 255f); buff[i + 2] = (byte)Math.Round(BitConverter.ToSingle(image_data, i * 2) * 255f);
buff[i + 3] = 255; buff[i + 3] = 255;
} }
return buff; return true;
} }
private byte[] DecodeRGBAFloat() private bool DecodeRGBAFloat(byte[] image_data, byte[] buff)
{ {
var buff = new byte[m_Width * m_Height * 4];
for (var i = 0; i < buff.Length; i += 4) for (var i = 0; i < buff.Length; i += 4)
{ {
buff[i] = (byte)Math.Round(BitConverter.ToSingle(image_data, i * 4 + 8) * 255f); buff[i] = (byte)Math.Round(BitConverter.ToSingle(image_data, i * 4 + 8) * 255f);
@ -438,7 +409,7 @@ namespace AssetStudio
buff[i + 2] = (byte)Math.Round(BitConverter.ToSingle(image_data, i * 4) * 255f); buff[i + 2] = (byte)Math.Round(BitConverter.ToSingle(image_data, i * 4) * 255f);
buff[i + 3] = (byte)Math.Round(BitConverter.ToSingle(image_data, i * 4 + 12) * 255f); buff[i + 3] = (byte)Math.Round(BitConverter.ToSingle(image_data, i * 4 + 12) * 255f);
} }
return buff; return true;
} }
private static byte ClampByte(int x) private static byte ClampByte(int x)
@ -446,9 +417,8 @@ namespace AssetStudio
return (byte)(byte.MaxValue < x ? byte.MaxValue : (x > byte.MinValue ? x : byte.MinValue)); return (byte)(byte.MaxValue < x ? byte.MaxValue : (x > byte.MinValue ? x : byte.MinValue));
} }
private byte[] DecodeYUY2() private bool DecodeYUY2(byte[] image_data, byte[] buff)
{ {
var buff = new byte[m_Width * m_Height * 4];
int p = 0; int p = 0;
int o = 0; int o = 0;
int halfWidth = m_Width / 2; int halfWidth = m_Width / 2;
@ -474,12 +444,11 @@ namespace AssetStudio
buff[o++] = 255; buff[o++] = 255;
} }
} }
return buff; return true;
} }
private byte[] DecodeRGB9e5Float() private bool DecodeRGB9e5Float(byte[] image_data, byte[] buff)
{ {
var buff = new byte[m_Width * m_Height * 4];
for (var i = 0; i < buff.Length; i += 4) for (var i = 0; i < buff.Length; i += 4)
{ {
var n = BitConverter.ToInt32(image_data, i); var n = BitConverter.ToInt32(image_data, i);
@ -493,195 +462,163 @@ namespace AssetStudio
buff[i + 2] = (byte)Math.Round(r * scalef * 255f); buff[i + 2] = (byte)Math.Round(r * scalef * 255f);
buff[i + 3] = 255; buff[i + 3] = 255;
} }
return buff; return true;
} }
private byte[] DecodeBC4() private bool DecodeBC4(byte[] image_data, byte[] buff)
{ {
var buff = new byte[m_Width * m_Height * 4]; return TextureDecoder.DecodeBC4(image_data, m_Width, m_Height, buff);
if (!TextureDecoder.DecodeBC4(image_data, m_Width, m_Height, buff))
{
return null;
}
return buff;
} }
private byte[] DecodeBC5() private bool DecodeBC5(byte[] image_data, byte[] buff)
{ {
var buff = new byte[m_Width * m_Height * 4]; return TextureDecoder.DecodeBC5(image_data, m_Width, m_Height, buff);
if (!TextureDecoder.DecodeBC5(image_data, m_Width, m_Height, buff))
{
return null;
}
return buff;
} }
private byte[] DecodeBC6H() private bool DecodeBC6H(byte[] image_data, byte[] buff)
{ {
var buff = new byte[m_Width * m_Height * 4]; return TextureDecoder.DecodeBC6(image_data, m_Width, m_Height, buff);
if (!TextureDecoder.DecodeBC6(image_data, m_Width, m_Height, buff))
{
return null;
}
return buff;
} }
private byte[] DecodeBC7() private bool DecodeBC7(byte[] image_data, byte[] buff)
{ {
var buff = new byte[m_Width * m_Height * 4]; return TextureDecoder.DecodeBC7(image_data, m_Width, m_Height, buff);
if (!TextureDecoder.DecodeBC7(image_data, m_Width, m_Height, buff))
{
return null;
}
return buff;
} }
private byte[] DecodePVRTC(bool is2bpp) private bool DecodeDXT1Crunched(byte[] image_data, byte[] buff)
{ {
var buff = new byte[m_Width * m_Height * 4]; if (UnpackCrunch(image_data, out var result))
if (!TextureDecoder.DecodePVRTC(image_data, m_Width, m_Height, buff, is2bpp))
{ {
return null; if (DecodeDXT1(result, buff))
{
return true;
} }
return buff; }
return false;
} }
private byte[] DecodeETC1() private bool DecodeDXT5Crunched(byte[] image_data, byte[] buff)
{ {
var buff = new byte[m_Width * m_Height * 4]; if (UnpackCrunch(image_data, out var result))
if (!TextureDecoder.DecodeETC1(image_data, m_Width, m_Height, buff))
{ {
return null; if (DecodeDXT5(result, buff))
{
return true;
} }
return buff; }
return false;
} }
private byte[] DecodeATCRGB4() private bool DecodePVRTC(byte[] image_data, byte[] buff, bool is2bpp)
{ {
var buff = new byte[m_Width * m_Height * 4]; return TextureDecoder.DecodePVRTC(image_data, m_Width, m_Height, buff, is2bpp);
if (!TextureDecoder.DecodeATCRGB4(image_data, m_Width, m_Height, buff))
{
return null;
}
return buff;
} }
private byte[] DecodeATCRGBA8() private bool DecodeETC1(byte[] image_data, byte[] buff)
{ {
var buff = new byte[m_Width * m_Height * 4]; return TextureDecoder.DecodeETC1(image_data, m_Width, m_Height, buff);
if (!TextureDecoder.DecodeATCRGBA8(image_data, m_Width, m_Height, buff))
{
return null;
}
return buff;
} }
private byte[] DecodeEACR() private bool DecodeATCRGB4(byte[] image_data, byte[] buff)
{ {
var buff = new byte[m_Width * m_Height * 4]; return TextureDecoder.DecodeATCRGB4(image_data, m_Width, m_Height, buff);
if (!TextureDecoder.DecodeEACR(image_data, m_Width, m_Height, buff))
{
return null;
}
return buff;
} }
private byte[] DecodeEACRSigned() private bool DecodeATCRGBA8(byte[] image_data, byte[] buff)
{ {
var buff = new byte[m_Width * m_Height * 4]; return TextureDecoder.DecodeATCRGBA8(image_data, m_Width, m_Height, buff);
if (!TextureDecoder.DecodeEACRSigned(image_data, m_Width, m_Height, buff))
{
return null;
}
return buff;
} }
private byte[] DecodeEACRG() private bool DecodeEACR(byte[] image_data, byte[] buff)
{ {
var buff = new byte[m_Width * m_Height * 4]; return TextureDecoder.DecodeEACR(image_data, m_Width, m_Height, buff);
if (!TextureDecoder.DecodeEACRG(image_data, m_Width, m_Height, buff))
{
return null;
}
return buff;
} }
private byte[] DecodeEACRGSigned() private bool DecodeEACRSigned(byte[] image_data, byte[] buff)
{ {
var buff = new byte[m_Width * m_Height * 4]; return TextureDecoder.DecodeEACRSigned(image_data, m_Width, m_Height, buff);
if (!TextureDecoder.DecodeEACRGSigned(image_data, m_Width, m_Height, buff))
{
return null;
}
return buff;
} }
private byte[] DecodeETC2() private bool DecodeEACRG(byte[] image_data, byte[] buff)
{ {
var buff = new byte[m_Width * m_Height * 4]; return TextureDecoder.DecodeEACRG(image_data, m_Width, m_Height, buff);
if (!TextureDecoder.DecodeETC2(image_data, m_Width, m_Height, buff))
{
return null;
}
return buff;
} }
private byte[] DecodeETC2A1() private bool DecodeEACRGSigned(byte[] image_data, byte[] buff)
{ {
var buff = new byte[m_Width * m_Height * 4]; return TextureDecoder.DecodeEACRGSigned(image_data, m_Width, m_Height, buff);
if (!TextureDecoder.DecodeETC2A1(image_data, m_Width, m_Height, buff))
{
return null;
}
return buff;
} }
private byte[] DecodeETC2A8() private bool DecodeETC2(byte[] image_data, byte[] buff)
{ {
var buff = new byte[m_Width * m_Height * 4]; return TextureDecoder.DecodeETC2(image_data, m_Width, m_Height, buff);
if (!TextureDecoder.DecodeETC2A8(image_data, m_Width, m_Height, buff))
{
return null;
}
return buff;
} }
private byte[] DecodeASTC(int blocksize) private bool DecodeETC2A1(byte[] image_data, byte[] buff)
{ {
var buff = new byte[m_Width * m_Height * 4]; return TextureDecoder.DecodeETC2A1(image_data, m_Width, m_Height, buff);
if (!TextureDecoder.DecodeASTC(image_data, m_Width, m_Height, blocksize, blocksize, buff))
{
return null;
}
return buff;
} }
private byte[] DecodeRG16() private bool DecodeETC2A8(byte[] image_data, byte[] buff)
{
return TextureDecoder.DecodeETC2A8(image_data, m_Width, m_Height, buff);
}
private bool DecodeASTC(byte[] image_data, byte[] buff, int blocksize)
{
return TextureDecoder.DecodeASTC(image_data, m_Width, m_Height, blocksize, blocksize, buff);
}
private bool DecodeRG16(byte[] image_data, byte[] buff)
{ {
var buff = new byte[m_Width * m_Height * 4];
for (var i = 0; i < m_Width * m_Height; i += 2) for (var i = 0; i < m_Width * m_Height; i += 2)
{ {
buff[i * 2] = 0; //B
buff[i * 2 + 1] = image_data[i + 1];//G buff[i * 2 + 1] = image_data[i + 1];//G
buff[i * 2 + 2] = image_data[i];//R buff[i * 2 + 2] = image_data[i];//R
buff[i * 2 + 3] = 255;//A buff[i * 2 + 3] = 255;//A
} }
return buff; return true;
} }
private byte[] DecodeR8() private bool DecodeR8(byte[] image_data, byte[] buff)
{ {
var buff = new byte[m_Width * m_Height * 4];
for (var i = 0; i < m_Width * m_Height; i++) for (var i = 0; i < m_Width * m_Height; i++)
{ {
buff[i * 4] = 0; //B
buff[i * 4 + 1] = 0; //G
buff[i * 4 + 2] = image_data[i];//R buff[i * 4 + 2] = image_data[i];//R
buff[i * 4 + 3] = 255;//A buff[i * 4 + 3] = 255;//A
} }
return buff; return true;
} }
private bool UnpackCrunch() private bool DecodeETC1Crunched(byte[] image_data, byte[] buff)
{
if (UnpackCrunch(image_data, out var result))
{
if (DecodeETC1(result, buff))
{
return true;
}
}
return false;
}
private bool DecodeETC2A8Crunched(byte[] image_data, byte[] buff)
{
if (UnpackCrunch(image_data, out var result))
{
if (DecodeETC2A8(result, buff))
{
return true;
}
}
return false;
}
private bool UnpackCrunch(byte[] image_data, out byte[] result)
{ {
byte[] result;
if (version[0] > 2017 || (version[0] == 2017 && version[1] >= 3) //2017.3 and up if (version[0] > 2017 || (version[0] == 2017 && version[1] >= 3) //2017.3 and up
|| m_TextureFormat == TextureFormat.ETC_RGB4Crunched || m_TextureFormat == TextureFormat.ETC_RGB4Crunched
|| m_TextureFormat == TextureFormat.ETC2_RGBA8Crunched) || m_TextureFormat == TextureFormat.ETC2_RGBA8Crunched)
@ -694,8 +631,6 @@ namespace AssetStudio
} }
if (result != null) if (result != null)
{ {
image_data = result;
image_data_size = result.Length;
return true; return true;
} }
return false; return false;

View File

@ -7,13 +7,15 @@ namespace AssetStudio
{ {
public static class Texture2DExtensions public static class Texture2DExtensions
{ {
public static Image ConvertToImage(this Texture2D m_Texture2D, bool flip) public static Image<Bgra32> ConvertToImage(this Texture2D m_Texture2D, bool flip)
{ {
var converter = new Texture2DConverter(m_Texture2D); var converter = new Texture2DConverter(m_Texture2D);
var bytes = converter.DecodeTexture2D(); var buff = BigArrayPool<byte>.Shared.Rent(m_Texture2D.m_Width * m_Texture2D.m_Height * 4);
if (bytes != null && bytes.Length > 0) try
{ {
var image = Image.LoadPixelData<Bgra32>(bytes, m_Texture2D.m_Width, m_Texture2D.m_Height); if (converter.DecodeTexture2D(buff))
{
var image = Image.LoadPixelData<Bgra32>(buff, m_Texture2D.m_Width, m_Texture2D.m_Height);
if (flip) if (flip)
{ {
image.Mutate(x => x.Flip(FlipMode.Vertical)); image.Mutate(x => x.Flip(FlipMode.Vertical));
@ -22,6 +24,11 @@ namespace AssetStudio
} }
return null; return null;
} }
finally
{
BigArrayPool<byte>.Shared.Return(buff);
}
}
public static MemoryStream ConvertToStream(this Texture2D m_Texture2D, ImageFormat imageFormat, bool flip) public static MemoryStream ConvertToStream(this Texture2D m_Texture2D, ImageFormat imageFormat, bool flip)
{ {