示例#1
0
        /// <summary>
        /// Function to load an image from a stream.
        /// </summary>
        /// <param name="stream">The stream containing the image data to read.</param>
        /// <param name="size">The size of the image within the stream, in bytes.</param>
        /// <returns>A <see cref="IGorgonImage"/> containing the image data from the stream.</returns>
        /// <exception cref="GorgonException">Thrown when the image data in the stream has a pixel format that is unsupported.</exception>
        protected override IGorgonImage OnDecodeFromStream(Stream stream, long size)
        {
            if (Unsafe.SizeOf <TgaHeader>() >= size)
            {
                throw new EndOfStreamException();
            }

            using (var reader = new GorgonBinaryReader(stream, true))
            {
                IGorgonImageInfo info = ReadHeader(reader, out TGAConversionFlags flags);

                IGorgonImage image = new GorgonImage(info);

                if (DecodingOptions?.SetZeroAlphaAsOpaque ?? true)
                {
                    flags |= TGAConversionFlags.SetOpaqueAlpha;
                }

                CopyImageData(reader, image, flags);

                stream.Position = size;

                return(image);
            }
        }
示例#2
0
        /// <summary>Function to determine if the content plugin can open the specified file.</summary>
        /// <param name="file">The content file to evaluate.</param>
        /// <param name="fileManager">The content file manager.</param>
        /// <returns>
        ///   <b>true</b> if the plugin can open the file, or <b>false</b> if not.</returns>
        /// <exception cref="ArgumentNullException">Thrown when the <paramref name="file" />, or the <paramref name="fileManager"/> parameter is <b>null</b>.</exception>
        public bool CanOpenContent(IContentFile file, IContentFileManager fileManager)
        {
            if (file == null)
            {
                throw new ArgumentNullException(nameof(file));
            }

            if (fileManager == null)
            {
                throw new ArgumentNullException(nameof(fileManager));
            }

            using (Stream stream = file.OpenRead())
            {
                if (!_ddsCodec.IsReadable(stream))
                {
                    return(false);
                }

                IGorgonImageInfo metadata = _ddsCodec.GetMetaData(stream);

                // We won't be supporting 1D images in this editor.
                if ((metadata.ImageType == ImageType.Image1D) || (metadata.ImageType == ImageType.Unknown))
                {
                    return(false);
                }

                UpdateFileMetadataAttributes(file.Metadata.Attributes);
                return(true);
            }
        }
示例#3
0
        /// <summary>Function to load the image file into memory.</summary>
        /// <param name="file">The stream for the file to load.</param>
        /// <param name="name">The name of the file.</param>
        /// <returns>The image data, the virtual file entry for the working file and the original pixel format of the file.</returns>
        public (IGorgonImage image, IGorgonVirtualFile workingFile, BufferFormat originalFormat) LoadImageFile(Stream file, string name)
        {
            IGorgonImage       result = null;
            IGorgonVirtualFile workFile;
            BufferFormat       originalFormat;

            IGorgonImageInfo imageInfo = DefaultCodec.GetMetaData(file);

            originalFormat = imageInfo.Format;
            var formatInfo = new GorgonFormatInfo(imageInfo.Format);

            // We absolutely need to have an extension, or else the texconv tool will not work.
            if ((DefaultCodec.CodecCommonExtensions.Count > 0) &&
                (!string.Equals(Path.GetExtension(name), DefaultCodec.CodecCommonExtensions[0], System.StringComparison.OrdinalIgnoreCase)))
            {
                _log.Print("Adding DDS extension to working file or else external tools may not be able to read it.", LoggingLevel.Verbose);
                name = Path.ChangeExtension(name, DefaultCodec.CodecCommonExtensions[0]);
            }

            _log.Print($"Copying content file {name} to {ScratchArea.FileSystem.MountPoints.First().PhysicalPath} as working file...", LoggingLevel.Intermediate);

            // Copy to a working file.
            using (Stream outStream = ScratchArea.OpenStream(name, FileMode.Create))
            {
                file.CopyTo(outStream);
                workFile = ScratchArea.FileSystem.GetFile(name);
            }

            _log.Print($"{workFile.FullPath} is now the working file for the image editor.", LoggingLevel.Intermediate);

            if (formatInfo.IsCompressed)
            {
                _log.Print($"Image is compressed using [{formatInfo.Format}] as its pixel format.", LoggingLevel.Intermediate);

                if (_compressor == null)
                {
                    throw new GorgonException(GorgonResult.CannotRead, string.Format(Resources.GORIMG_ERR_COMPRESSED_FILE, formatInfo.Format));
                }

                _log.Print($"Loading image '{workFile.FullPath}'...", LoggingLevel.Simple);
                result = _compressor.Decompress(ref workFile, imageInfo);

                if (result == null)
                {
                    throw new GorgonException(GorgonResult.CannotRead, string.Format(Resources.GORIMG_ERR_COMPRESSED_FILE, formatInfo.Format));
                }

                _log.Print($"Loaded compressed ([{formatInfo.Format}]) image data as [{result.Format}]", LoggingLevel.Intermediate);
            }
            else
            {
                _log.Print($"Loading image '{workFile.FullPath}'...", LoggingLevel.Simple);
                using (Stream workingFileStream = workFile.OpenStream())
                {
                    result = DefaultCodec.LoadFromStream(workingFileStream);
                }
            }

            return(result, workFile, originalFormat);
        }
示例#4
0
        /// <summary>
        /// Function to write out the DDS header to the stream.
        /// </summary>
        /// <param name="settings">Meta data for the image header.</param>
        /// <param name="conversionFlags">Flags required for image conversion.</param>
        /// <returns>A TGA header value, populated with the correct settings.</returns>
        private TgaHeader GetHeader(IGorgonImageInfo settings, out TGAConversionFlags conversionFlags)
        {
            TgaHeader header = default;

            conversionFlags = TGAConversionFlags.None;

            if ((settings.Width > 0xFFFF) ||
                (settings.Height > 0xFFFF))
            {
                throw new IOException(string.Format(Resources.GORIMG_ERR_FILE_FORMAT_NOT_CORRECT, Codec));
            }

            header.Width  = (ushort)(settings.Width);
            header.Height = (ushort)(settings.Height);

            switch (settings.Format)
            {
            case BufferFormat.R8G8B8A8_UNorm:
            case BufferFormat.R8G8B8A8_UNorm_SRgb:
                header.ImageType  = TgaImageType.TrueColor;
                header.BPP        = 32;
                header.Descriptor = TgaDescriptor.InvertY | TgaDescriptor.RGB888A8;
                conversionFlags  |= TGAConversionFlags.Swizzle;
                break;

            case BufferFormat.B8G8R8A8_UNorm:
            case BufferFormat.B8G8R8A8_UNorm_SRgb:
                header.ImageType  = TgaImageType.TrueColor;
                header.BPP        = 32;
                header.Descriptor = TgaDescriptor.InvertY | TgaDescriptor.RGB888A8;
                break;

            case BufferFormat.B8G8R8X8_UNorm:
            case BufferFormat.B8G8R8X8_UNorm_SRgb:
                header.ImageType  = TgaImageType.TrueColor;
                header.BPP        = 24;
                header.Descriptor = TgaDescriptor.InvertY;
                conversionFlags  |= TGAConversionFlags.RGB888;
                break;

            case BufferFormat.R8_UNorm:
            case BufferFormat.A8_UNorm:
                header.ImageType  = TgaImageType.BlackAndWhite;
                header.BPP        = 8;
                header.Descriptor = TgaDescriptor.InvertY;
                break;

            case BufferFormat.B5G5R5A1_UNorm:
                header.ImageType  = TgaImageType.TrueColor;
                header.BPP        = 16;
                header.Descriptor = TgaDescriptor.InvertY | TgaDescriptor.RGB555A1;
                break;

            default:
                throw new IOException(string.Format(Resources.GORIMG_ERR_FORMAT_NOT_SUPPORTED, settings.Format));
            }

            // Persist to stream.
            return(header);
        }
        /// <summary>
        /// Function to decompress a block compressed file into a standard pixel format.
        /// </summary>
        /// <param name="imageFile">The file to decompress</param>
        /// <param name="metadata">The metadata for the file.</param>
        /// <returns>The decompressed image.</returns>
        public IGorgonImage Decompress(ref IGorgonVirtualFile imageFile, IGorgonImageInfo metadata)
        {
            string             directory      = Path.GetDirectoryName(imageFile.PhysicalFile.FullPath);
            IGorgonImage       result         = null;
            Process            texConvProcess = null;
            Stream             inStream       = null;
            IGorgonVirtualFile decodedFile    = null;

            var info = new ProcessStartInfo
            {
                Arguments        = $"-f {_d3dFormats[metadata.Format]} -y -m {metadata.MipCount} -fl 12.0 -ft DDS -o \"{directory}\" -nologo -px decoded {imageFile.PhysicalFile.FullPath}\"",
                ErrorDialog      = true,
                FileName         = _texConv.FullName,
                WorkingDirectory = directory,
                UseShellExecute  = false,
#if DEBUG
                CreateNoWindow = false,
#else
                CreateNoWindow = true,
#endif
                RedirectStandardError  = true,
                RedirectStandardOutput = true
            };

            try
            {
                texConvProcess = Process.Start(info);

                if (texConvProcess == null)
                {
                    return(null);
                }

                texConvProcess.WaitForExit();

                _writer.FileSystem.Refresh();
                imageFile = _writer.FileSystem.GetFile(imageFile.FullPath);

                decodedFile = _writer.FileSystem.GetFile(imageFile.Directory.FullPath + "decoded" + imageFile.Name);
                inStream    = decodedFile.OpenStream();
                result      = _codec.LoadFromStream(inStream);

                return(result);
            }
            finally
            {
                texConvProcess?.Dispose();
                inStream?.Dispose();

                if (decodedFile != null)
                {
                    _writer.DeleteFile(decodedFile.FullPath);
                }
            }
        }
示例#6
0
        /// <summary>
        /// Function to decode an image from a <see cref="Stream"/>.
        /// </summary>
        /// <param name="stream">The stream containing the image data to read.</param>
        /// <param name="size">The size of the image within the stream, in bytes.</param>
        /// <returns>A <see cref="IGorgonImage"/> containing the image data from the stream.</returns>
        /// <exception cref="GorgonException">Thrown when the image data in the stream has a pixel format that is unsupported.</exception>
        /// <remarks>
        /// <para>
        /// A codec must implement this method in order to decode the image data.
        /// </para>
        /// </remarks>
        protected override IGorgonImage OnDecodeFromStream(Stream stream, long size)
        {
            // Read the image meta data so we'll know how large the data should be.
            IGorgonImageInfo settings = ReadMetaData(stream);

            // Calculate the expected size of the image.
            int dataSize = settings.Width * settings.Height * sizeof(ushort);

            if ((size - TvHeader.SizeInBytes) != dataSize)
            {
                throw new GorgonException(GorgonResult.CannotRead, "The data in the stream is not the same size as the proposed image size.");
            }

            // Create our resulting image buffer.
            var result = new GorgonImage(settings);

            using (var reader = new GorgonBinaryReader(stream, true))
            {
                // Write each scanline.
                for (int y = 0; y < settings.Height; ++y)
                {
                    // Ensure that we move to the next line by the row pitch and not the amount of pixels.
                    // Some images put padding in for alignment reasons which can throw off the data offsets.
                    int ptrPosition = (y * result.Buffers[0].PitchInformation.RowPitch);

                    // Decode the pixels in the scan line for our resulting image.
                    for (int x = 0; x < settings.Width; ++x)
                    {
                        // Get our current pixel.
                        ushort pixel = reader.ReadUInt16();

                        // Since we encode 1 byte per color component for each pixel, we need to bump up the bit shift
                        // by 8 bits.  Once we get above 24 bits we'll start over since we're only working with 4 bytes
                        // per pixel in the destination.

                        // We determine how many bits to shift the pixel based on horizontal positioning.
                        // We assume that the image is based on 4 bytes/pixel.  In most cases this value should be
                        // determined by dividing the row pitch by the image width.

                        // Write the color by shifting the byte in the source data to the appropriate byte position.
                        uint color = (uint)(((pixel >> 8) & 0xff) << (8 * (x % 3)));
                        uint alpha = (uint)((pixel & 0xff) << 24);

                        ref uint imagePixel = ref result.ImageData.ReadAs <uint>(ptrPosition);
                        imagePixel   = color | alpha;
                        ptrPosition += sizeof(uint);
                    }
                }
            }
示例#7
0
        /// <summary>
        /// Function to determine if the form can be shown.
        /// </summary>
        /// <returns><b>true</b> if the form can be shown, <b>false</b> if not.</returns>
        private bool CanShowForm()
        {
            if ((string.IsNullOrWhiteSpace(_fileManager?.CurrentDirectory)) ||
                (_fileManager.SelectedFile == null))
            {
                return(false);
            }

            if ((_fileManager.SelectedFile.Metadata == null) ||
                (!_fileManager.SelectedFile.Metadata.Attributes.TryGetValue(CommonEditorConstants.ContentTypeAttr, out string contentType)) ||
                (!string.Equals(contentType, CommonEditorContentTypes.ImageType, StringComparison.OrdinalIgnoreCase)))
            {
                return(false);
            }

            Stream fileStream = null;

            try
            {
                fileStream = _fileManager.SelectedFile.OpenRead();
                if (!_defaultImageCodec.IsReadable(fileStream))
                {
                    return(false);
                }

                IGorgonImageInfo metaData = _defaultImageCodec.GetMetaData(fileStream);

                if ((metaData.ImageType != ImageType.Image2D) && (metaData.ImageType != ImageType.ImageCube))
                {
                    return(false);
                }
            }
            catch (Exception ex)
            {
                CommonServices.Log.Print($"[ERROR] Cannot open the selected file {_fileManager.SelectedFile.Name}.", LoggingLevel.Simple);
                CommonServices.Log.LogException(ex);
            }
            finally
            {
                fileStream?.Dispose();
            }

            return(true);
        }
示例#8
0
        /// <summary>
        /// Initializes a new instance of the <see cref="GorgonImageInfo"/> class.
        /// </summary>
        /// <param name="info">The initial image information to copy into this instance.</param>
        /// <param name="imageType">[Optional] An updated image type.</param>
        /// <param name="format">[Optional] An updated image pixel format.</param>
        /// <exception cref="ArgumentNullException">Thrown when the <paramref name="info"/> parameter is <b>null</b>.</exception>
        /// <exception cref="ArgumentException">Thrown when the <see cref="Format"/> value of the <paramref name="info"/> parameter is set to <see cref="BufferFormat.Unknown"/>.</exception>
        /// <remarks>
        /// <para>
        /// Use this constructor to create a copy of an existing <see cref="IGorgonImageInfo"/> object.
        /// </para>
        /// </remarks>
        public GorgonImageInfo(IGorgonImageInfo info, ImageType?imageType = null, BufferFormat?format = null)
        {
            if (info == null)
            {
                throw new ArgumentNullException(nameof(info));
            }

            if (info.Format == BufferFormat.Unknown)
            {
                throw new ArgumentException(string.Format(Resources.GORIMG_ERR_FORMAT_NOT_SUPPORTED, info.Format), nameof(info.Format));
            }

            Format                = format ?? info.Format;
            ArrayCount            = info.ArrayCount.Max(1);
            Depth                 = info.Depth;
            Height                = info.Height;
            Width                 = info.Width;
            ImageType             = imageType ?? info.ImageType;
            MipCount              = info.MipCount.Max(1);
            HasPreMultipliedAlpha = info.HasPreMultipliedAlpha;
        }
示例#9
0
        /// <summary>
        /// Function to import an image file from the physical file system into the current image.
        /// </summary>
        /// <param name="codec">The codec used to open the file.</param>
        /// <param name="filePath">The path to the file to import.</param>
        /// <returns>The source file information, image data, the virtual file entry for the working file and the original pixel format of the file.</returns>
        public (FileInfo file, IGorgonImage image, IGorgonVirtualFile workingFile, BufferFormat originalFormat) ImportImage(IGorgonImageCodec codec, string filePath)
        {
            var file = new FileInfo(filePath);
            IGorgonImageCodec  importCodec  = codec;
            IGorgonImageInfo   metaData     = null;
            IGorgonVirtualFile workFile     = null;
            IGorgonImage       importImage  = null;
            string             workFilePath = $"{Path.GetFileNameWithoutExtension(filePath)}_import_{Guid.NewGuid().ToString("N")}";

            // Try to determine if we can actually read the file using an installed codec, if we can't, then try to find a suitable codec.
            using (FileStream stream = File.Open(filePath, FileMode.Open, FileAccess.Read, FileShare.Read))
            {
                if ((importCodec == null) || (!importCodec.IsReadable(stream)))
                {
                    importCodec = null;

                    foreach (IGorgonImageCodec newCodec in InstalledCodecs.Codecs.Where(item => (item.CodecCommonExtensions.Count > 0) && (item.CanDecode)))
                    {
                        if (newCodec.IsReadable(stream))
                        {
                            importCodec = newCodec;
                            break;
                        }
                    }
                }

                if (importCodec == null)
                {
                    throw new GorgonException(GorgonResult.CannotRead, string.Format(Resources.GORIMG_ERR_NO_CODEC, filePath));
                }

                metaData = importCodec.GetMetaData(stream);


                // We absolutely need to have an extension, or else the texconv tool will not work.
                var codecExtension = new GorgonFileExtension(importCodec.CodecCommonExtensions[0]);
                _log.Print($"Adding {codecExtension.Extension} extension to working file or else external tools may not be able to read it.", LoggingLevel.Verbose);
                workFilePath = $"{workFilePath}.{codecExtension.Extension}";

                using (Stream outStream = ScratchArea.OpenStream(workFilePath, FileMode.Create))
                {
                    stream.CopyTo(outStream);
                }
            }

            workFile = ScratchArea.FileSystem.GetFile(workFilePath);
            var formatInfo = new GorgonFormatInfo(metaData.Format);

            // This is always in DDS format.
            if (formatInfo.IsCompressed)
            {
                _log.Print($"Image is compressed using [{formatInfo.Format}] as its pixel format.", LoggingLevel.Intermediate);

                if (_compressor == null)
                {
                    throw new GorgonException(GorgonResult.CannotRead, string.Format(Resources.GORIMG_ERR_COMPRESSED_FILE, formatInfo.Format));
                }

                _log.Print($"Loading image '{workFile.FullPath}'...", LoggingLevel.Simple);
                importImage = _compressor.Decompress(ref workFile, metaData);

                if (importImage == null)
                {
                    throw new GorgonException(GorgonResult.CannotRead, string.Format(Resources.GORIMG_ERR_COMPRESSED_FILE, formatInfo.Format));
                }

                _log.Print($"Loaded compressed ([{formatInfo.Format}]) image data as [{importImage.Format}]", LoggingLevel.Intermediate);
            }
            else
            {
                using (Stream workStream = workFile.OpenStream())
                {
                    importImage = importCodec.LoadFromStream(workStream);
                }
            }

            return(file, importImage, workFile, metaData.Format);
        }
示例#10
0
 /// <summary>
 /// Function to retrieve custom metadata when encoding an image frame.
 /// </summary>
 /// <param name="frameIndex">The index of the frame being encoded.</param>
 /// <param name="settings">The settings for the image being encoded.</param>
 /// <returns>A dictionary containing the key/value pair describing the metadata to write to the frame, or <b>null</b> if the frame contains no metadata.</returns>
 protected virtual IReadOnlyDictionary <string, object> GetCustomEncodingMetadata(int frameIndex, IGorgonImageInfo settings) => null;
示例#11
0
        /// <summary>
        /// Function to retrieve custom metadata when encoding an image frame.
        /// </summary>
        /// <param name="frameIndex">The index of the frame being encoded.</param>
        /// <param name="settings">The settings for the image being encoded.</param>
        /// <returns>A dictionary containing the key/value pair describing the metadata to write to the frame, or <b>null</b> if the frame contains no metadata.</returns>
        protected override IReadOnlyDictionary <string, object> GetCustomEncodingMetadata(int frameIndex, IGorgonImageInfo settings)
        {
            var result = new Dictionary <string, object>();

            if (EncodingOptions?.Palette != null)
            {
                for (int i = 0; i < EncodingOptions.Palette.Count; ++i)
                {
                    if (!EncodingOptions.Palette[i].Alpha.EqualsEpsilon(0))
                    {
                        continue;
                    }

                    result["/grctlext/TransparencyFlag"]      = true;
                    result["/grctlext/TransparentColorIndex"] = i;
                }
            }

            bool saveAllFrames = EncodingOptions?.SaveAllFrames ?? true;

            if ((settings == null) ||
                (settings.ArrayCount < 2) ||
                (!saveAllFrames))
            {
                return(result.Count == 0 ? null : result);
            }

            // Write out frame delays.
            ushort delayValue = 0;

            if ((EncodingOptions?.FrameDelays != null) && (frameIndex >= 0) && (frameIndex < EncodingOptions.FrameDelays.Count))
            {
                delayValue = (ushort)EncodingOptions.FrameDelays[frameIndex];
            }

            result["/grctlext/Delay"]    = delayValue;
            result["/grctlext/Disposal"] = (ushort)1;

            return(result);
        }