예제 #1
0
        private async Task DuplicateSmallerImageIfLargeAndSave(byte[] formFileContent, string name, int ifLargerThan, int constrainX, int constrainY)
        {
            using (var job = new ImageJob())
            {
                var info = await ImageJob.GetImageInfo(new BytesSource(formFileContent));

                if (info.ImageWidth > ifLargerThan)
                {
                    GetEncoder(info, out IEncoderPreset encoder, out string expectedExt);

                    var resized = await job.Decode(formFileContent)
                                  .ConstrainWithin((uint)constrainX, (uint)constrainY)
                                  .EncodeToBytes(encoder)
                                  .Finish()
                                  .InProcessAndDisposeAsync();

                    var result = resized.First.TryGetBytes();

                    if (result.HasValue)
                    {
                        var fileName = Path.GetFileName(name);
                        var ext      = Path.GetExtension(fileName);

                        var newFileName = name.Replace(ext, "-sm" + expectedExt);

                        await _blog.SaveFile(result.Value.Array, newFileName);
                    }
                }
            }
        }
예제 #2
0
        private async Task <string> EncodeToPreferredFormatAndSave(byte[] formFileContent, string name)
        {
            using (var job = new ImageJob())
            {
                var info = await ImageJob.GetImageInfo(new BytesSource(formFileContent));

                GetEncoder(info, out IEncoderPreset encoder, out string expectedExt);

                var resized = await job.Decode(formFileContent)
                              .EncodeToBytes(encoder)
                              .Finish()
                              .InProcessAndDisposeAsync();

                var result = resized.First.TryGetBytes();

                if (result.HasValue)
                {
                    var fileName = Path.GetFileName(name);
                    var ext      = Path.GetExtension(fileName);

                    var newFileName = name.Replace(ext, expectedExt);

                    return(await _blog.SaveFile(result.Value.Array, newFileName));
                }
            }

            return(null);
        }
예제 #3
0
        static async Task Main(string[] args)
        {
            // From https://github.com/imazen/imageflow-dotnet#getting-image-dimensions-and-format
            var imageBytes = Convert.FromBase64String("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEX/TQBcNTh/AAAAAXRSTlPM0jRW/QAAAApJREFUeJxjYgAAAAYAAzY3fKgAAAAASUVORK5CYII=");
            var info       = await ImageJob.GetImageInfo(new BytesSource(imageBytes));

            Console.WriteLine(info.ImageWidth);
            Console.Read();
        }
예제 #4
0
        public async void TestPresets()
        {
            using (var contentRoot = new TempContentRoot()
                                     .AddResource("images/fire.jpg", "TestFiles.fire-umbrella-small.jpg"))
            {
                var hostBuilder = new HostBuilder()
                                  .ConfigureWebHost(webHost =>
                {
                    // Add TestServer
                    webHost.UseTestServer();
                    webHost.Configure(app =>
                    {
                        app.UseImageflow(new ImageflowMiddlewareOptions()
                                         .SetMapWebRoot(false)
                                         // Maps / to ContentRootPath/images
                                         .MapPath("/", Path.Combine(contentRoot.PhysicalPath, "images"))
                                         .AddPreset(new PresetOptions("tiny", PresetPriority.OverrideQuery)
                                                    .SetCommand("width", "2")
                                                    .SetCommand("height", "1"))
                                         .AddPreset(new PresetOptions("small", PresetPriority.DefaultValues)
                                                    .SetCommand("width", "30")
                                                    .SetCommand("height", "20"))
                                         );
                    });
                });

                // Build and start the IHost
                using var host = await hostBuilder.StartAsync();

                // Create an HttpClient to send requests to the TestServer
                using var client = host.GetTestClient();

                using var presetValidResponse = await client.GetAsync("/fire.jpg?preset=small&height=35&mode=pad");

                presetValidResponse.EnsureSuccessStatusCode();
                var responseBytes = await presetValidResponse.Content.ReadAsByteArrayAsync();

                var imageResults = await ImageJob.GetImageInfo(new BytesSource(responseBytes));

                Assert.Equal(30, imageResults.ImageWidth);
                Assert.Equal(35, imageResults.ImageHeight);


                using var presetTinyResponse = await client.GetAsync("/fire.jpg?preset=tiny&height=35");

                presetTinyResponse.EnsureSuccessStatusCode();
                responseBytes = await presetTinyResponse.Content.ReadAsByteArrayAsync();

                imageResults = await ImageJob.GetImageInfo(new BytesSource(responseBytes));

                Assert.Equal(2, imageResults.ImageWidth);
                Assert.Equal(1, imageResults.ImageHeight);

                await host.StopAsync(CancellationToken.None);
            }
        }
예제 #5
0
        public async void TestGetImageInfo()
        {
            var imageBytes = Convert.FromBase64String(
                "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEX/TQBcNTh/AAAAAXRSTlPM0jRW/QAAAApJREFUeJxjYgAAAAYAAzY3fKgAAAAASUVORK5CYII=");

            var info = await ImageJob.GetImageInfo(new BytesSource(imageBytes));

            Assert.Equal(info.ImageWidth, 1);
            Assert.Equal(info.ImageHeight, 1);
            Assert.Equal(info.PreferredExtension, "png");
            Assert.Equal(info.PreferredMimeType, "image/png");
            Assert.Equal(info.FrameDecodesInto, PixelFormat.Bgra_32);
        }
예제 #6
0
        /// <summary>
        /// Checks if an uploaded image meets the minimum dimensions. Will throw a ImageTooSmallException if not.
        /// </summary>
        private static async Task CheckImageDimensionsAsync(Stream imageStream)
        {
            var info = await ImageJob.GetImageInfo(new BytesSource(Utilities.ConvertStreamToBytes(imageStream)));

            var orientation = info.ImageWidth >= info.ImageHeight
                ? ImageOrientation.Landscape
                : ImageOrientation.Portrait;

            var imageTooSmallErrorMessage = $"Image must be equal or bigger than 800px on the longest side. Detected size {info.ImageWidth}x{info.ImageHeight}";

            switch (orientation)
            {
            case ImageOrientation.Landscape when info.ImageWidth < 800:
                throw new ImageTooSmallException(imageTooSmallErrorMessage);

            case ImageOrientation.Portrait when info.ImageHeight < 800:
                throw new ImageTooSmallException(imageTooSmallErrorMessage);
            }

            imageStream.Position = 0;
        }
        private Task <Result <Image> > ConvertAndUpload(Image dbImage, FormFile uploadedFile)
        {
            return(GetBytes()
                   .Ensure(AreDimensionsValid,
                           $"Uploading image size must be at least {MinimumImageWidth}×{MinimumImageHeight} pixels and the width mustn't exceed two heights and vice versa")
                   .Bind(Convert)
                   .Bind(Upload));


            Result <byte[]> GetBytes()
            {
                using var binaryReader = new BinaryReader(uploadedFile.OpenReadStream());
                return(Result.Success(binaryReader.ReadBytes((int)uploadedFile.Length)));
            }

            async Task <bool> AreDimensionsValid(byte[] imageBytes)
            {
                var info = await ImageJob.GetImageInfo(new BytesSource(imageBytes));

                return(MinimumImageWidth <= info.ImageWidth &&
                       MinimumImageHeight <= info.ImageHeight &&
                       info.ImageWidth / info.ImageHeight < 2 &&
                       info.ImageHeight / info.ImageWidth < 2);
            }

            async Task <Result <ImageSet> > Convert(byte[] imageBytes)
            {
                var imagesSet = new ImageSet();

                using var imageJob = new ImageJob();
                var jobResult = await imageJob.Decode(imageBytes)
                                .Constrain(new Constraint(ConstraintMode.Within, ResizedLargeImageMaximumSideSize, ResizedLargeImageMaximumSideSize))
                                .Branch(f => f.ConstrainWithin(ResizedSmallImageMaximumSideSize, ResizedSmallImageMaximumSideSize).EncodeToBytes(new MozJpegEncoder(TargetJpegQuality, true)))
                                .EncodeToBytes(new MozJpegEncoder(TargetJpegQuality, true))
                                .Finish().InProcessAsync();

                imagesSet.SmallImage = GetImage(1);
                imagesSet.MainImage  = GetImage(2);

                return(imagesSet.MainImage.Any() && imagesSet.SmallImage.Any()
                    ? Result.Success(imagesSet)
                    : Result.Failure <ImageSet>("Processing of the images failed"));


                byte[] GetImage(int index)
                {
                    var encodeResult = jobResult?.TryGet(index);
                    var bytes        = encodeResult?.TryGetBytes();

                    return(bytes != null?bytes.Value.ToArray() : new byte[]
                    {
                    });
                }
            }

            async Task <Result <Image> > Upload(ImageSet imageSet)
            {
                dbImage.Position = _dbContext.Images.Count(i => i.ReferenceId == dbImage.ReferenceId && i.ImageType == dbImage.ImageType);
                var entry = _dbContext.Images.Add(dbImage);
                await _dbContext.SaveChangesAsync();

                var imageId = entry.Entity.Id;

                SetImageKeys();

                var addToBucketResult = await AddImagesToBucket();

                if (!addToBucketResult)
                {
                    _dbContext.Images.Remove(entry.Entity);

                    await _dbContext.SaveChangesAsync();

                    return(Result.Failure <Image>("Uploading of the image failed"));
                }

                _dbContext.Images.Update(entry.Entity);

                await _dbContext.SaveChangesAsync();

                _dbContext.DetachEntry(entry.Entity);

                return(Result.Success(dbImage));


                void SetImageKeys()
                {
                    var basePartOfKey = $"{S3FolderName}/{dbImage.ServiceSupplierId}/{imageId}";

                    dbImage.Keys.MainImage  = $"{basePartOfKey}-main.jpg";
                    dbImage.Keys.SmallImage = $"{basePartOfKey}-small.jpg";
                }

                async Task <bool> AddImagesToBucket()
                {
                    await using var largeStream = new MemoryStream(imageSet.MainImage);
                    await using var smallStream = new MemoryStream(imageSet.SmallImage);
                    var imageList = new List <(string key, Stream stream)>
                    {
                        (dbImage.Keys.MainImage, largeStream),
                        (dbImage.Keys.SmallImage, smallStream)
                    };

                    var resultList = await _amazonS3ClientService.Add(_bucketName, imageList);

                    foreach (var result in resultList)
                    {
                        if (result.IsFailure)
                        {
                            var keyList = new List <string>
                            {
                                dbImage.Keys.MainImage,
                                dbImage.Keys.SmallImage
                            };
                            await _amazonS3ClientService.Delete(_bucketName, keyList);

                            return(false);
                        }
                    }

                    return(true);
                }
            }
        }
예제 #8
0
        /// <summary>
        /// Images contain metadata that describes the photo to varying degrees. This method extracts the metadata
        /// and parses out the most interesting pieces we're interested in and assigns it to the image object so we can
        /// present the information to the user and use it to help with searches.
        /// </summary>
        /// <param name="image">The Image object to assign the metadata to.</param>
        /// <param name="imageBytes">The byte array containing the recently-uploaded image file to inspect for metadata.</param>
        /// <param name="overwriteImageProperties">Specifies whether or not to update image properties from metadata that already have values.</param>
        /// <param name="log">Optionally pass in an ILogger instance to enable internal logging</param>
        public static async Task ParseAndAssignImageMetadata(Image image, byte[] imageBytes, bool overwriteImageProperties, ILogger log = null)
        {
            var stopwatch = new Stopwatch();

            stopwatch.Start();

            // whilst image dimensions can be extracted from metadata in some cases, not in every case and this isn't acceptable
            var info = await ImageJob.GetImageInfo(new BytesSource(imageBytes));

            image.Metadata.Width  = Convert.ToInt32(info.ImageWidth);
            image.Metadata.Height = Convert.ToInt32(info.ImageHeight);

            using var imageStream = new MemoryStream(imageBytes);
            imageStream.Position  = 0;
            var directories = ImageMetadataReader.ReadMetadata(imageStream);

            image.Metadata.TakenDate = GetImageDateTaken(directories);
            if (image.Metadata.TakenDate.HasValue)
            {
                // overwrite the image created date with when the photo was taken
                image.Created = image.Metadata.TakenDate.Value;
            }

            var iso = GetImageIso(directories);

            if (iso.HasValue)
            {
                image.Metadata.Iso = iso.Value;
            }

            if (!image.Credit.HasValue() || overwriteImageProperties)
            {
                var credit = GetImageCredit(directories);
                if (credit.HasValue())
                {
                    image.Credit = credit;
                }
            }

            var exifIfd0Directory = directories.OfType <ExifIfd0Directory>().FirstOrDefault();

            if (exifIfd0Directory != null)
            {
                var make = exifIfd0Directory.Tags.SingleOrDefault(t => t.Type == ExifDirectoryBase.TagMake);
                if (make != null && make.Description.HasValue())
                {
                    image.Metadata.CameraMake = make.Description;
                }

                var model = exifIfd0Directory.Tags.SingleOrDefault(t => t.Type == ExifDirectoryBase.TagModel);
                if (model != null && model.Description.HasValue())
                {
                    image.Metadata.CameraModel = model.Description;
                }

                if (!image.Caption.HasValue() || overwriteImageProperties)
                {
                    var imageDescription = exifIfd0Directory.Tags.SingleOrDefault(t => t.Type == ExifDirectoryBase.TagImageDescription);
                    if (imageDescription != null && imageDescription.Description.HasValue())
                    {
                        image.Caption = imageDescription.Description;
                    }
                }
            }

            var exifSubIfdDirectory = directories.OfType <ExifSubIfdDirectory>().FirstOrDefault();

            if (exifSubIfdDirectory != null)
            {
                var exposureTime = exifSubIfdDirectory.Tags.SingleOrDefault(t => t.Type == ExifDirectoryBase.TagExposureTime);
                // ReSharper disable once ConditionIsAlwaysTrueOrFalse -- wrong, can return null
                if (exposureTime != null && exposureTime.Description.HasValue())
                {
                    image.Metadata.ExposureTime = exposureTime.Description;
                }

                var aperture = exifSubIfdDirectory.Tags.SingleOrDefault(t => t.Type == ExifDirectoryBase.TagAperture);
                // ReSharper disable once ConditionIsAlwaysTrueOrFalse -- wrong, can return null
                if (aperture != null && aperture.Description.HasValue())
                {
                    image.Metadata.Aperture = aperture.Description;
                }

                var exposureBias = exifSubIfdDirectory.Tags.SingleOrDefault(t => t.Type == ExifDirectoryBase.TagExposureBias);
                // ReSharper disable once ConditionIsAlwaysTrueOrFalse -- wrong, can return null
                if (exposureBias != null && exposureBias.Description.HasValue())
                {
                    image.Metadata.ExposureBias = exposureBias.Description;
                }

                var meteringMode = exifSubIfdDirectory.Tags.SingleOrDefault(t => t.Type == ExifDirectoryBase.TagMeteringMode);
                // ReSharper disable once ConditionIsAlwaysTrueOrFalse -- wrong, can return null
                if (meteringMode != null && meteringMode.Description.HasValue())
                {
                    image.Metadata.MeteringMode = meteringMode.Description;
                }

                var flash = exifSubIfdDirectory.Tags.SingleOrDefault(t => t.Type == ExifDirectoryBase.TagFlash);
                // ReSharper disable once ConditionIsAlwaysTrueOrFalse -- wrong, can return null
                if (flash != null && flash.Description.HasValue())
                {
                    image.Metadata.Flash = flash.Description;
                }

                var focalLength = exifSubIfdDirectory.Tags.SingleOrDefault(t => t.Type == ExifDirectoryBase.TagFocalLength);
                // ReSharper disable once ConditionIsAlwaysTrueOrFalse -- wrong, can return null
                if (focalLength != null && focalLength.Description.HasValue())
                {
                    image.Metadata.FocalLength = focalLength.Description;
                }

                var lensMake = exifSubIfdDirectory.Tags.SingleOrDefault(t => t.Type == ExifDirectoryBase.TagLensMake);
                // ReSharper disable once ConditionIsAlwaysTrueOrFalse -- wrong, can return null
                if (lensMake != null && lensMake.Description.HasValue())
                {
                    image.Metadata.LensMake = lensMake.Description;
                }

                var lensModel = exifSubIfdDirectory.Tags.SingleOrDefault(t => t.Type == ExifDirectoryBase.TagLensModel);
                // ReSharper disable once ConditionIsAlwaysTrueOrFalse -- wrong, can return null
                if (lensModel != null && lensModel.Description.HasValue())
                {
                    image.Metadata.LensModel = lensModel.Description;
                }

                var whiteBalance = exifSubIfdDirectory.Tags.SingleOrDefault(t => t.Type == ExifDirectoryBase.TagWhiteBalance);
                // ReSharper disable once ConditionIsAlwaysTrueOrFalse -- wrong, can return null
                if (whiteBalance != null && whiteBalance.Description.HasValue())
                {
                    image.Metadata.WhiteBalance = whiteBalance.Description;
                }

                var whiteBalanceMode = exifSubIfdDirectory.Tags.SingleOrDefault(t => t.Type == ExifDirectoryBase.TagWhiteBalanceMode);
                // ReSharper disable once ConditionIsAlwaysTrueOrFalse -- wrong, can return null
                if (whiteBalanceMode != null && whiteBalanceMode.Description.HasValue())
                {
                    image.Metadata.WhiteBalanceMode = whiteBalanceMode.Description;
                }
            }

            var gpsDirectory = directories.OfType <GpsDirectory>().FirstOrDefault();
            var location     = gpsDirectory?.GetGeoLocation();

            if (location != null)
            {
                image.Metadata.LocationLatitude  = location.Latitude;
                image.Metadata.LocationLongitude = location.Longitude;
            }

            var iptcDirectory = directories.OfType <IptcDirectory>().FirstOrDefault();

            if (iptcDirectory != null)
            {
                var objectName = iptcDirectory.Tags.SingleOrDefault(t => t.Type == IptcDirectory.TagObjectName);
                if (objectName != null && objectName.Description.HasValue())
                {
                    image.Name = Utilities.TidyImageName(objectName.Description);
                }

                var keywords = iptcDirectory.GetKeywords();
                if (keywords != null)
                {
                    foreach (var keyword in keywords.Where(k => k.HasValue()))
                    {
                        image.TagsCsv = Utilities.AddTagToCsv(image.TagsCsv, keyword);
                    }
                }

                if (!image.Metadata.Location.HasValue() || overwriteImageProperties)
                {
                    var objectLocation = iptcDirectory.Tags.SingleOrDefault(t => t.Type == IptcDirectory.TagSubLocation);
                    if (objectLocation != null && objectLocation.Description.HasValue())
                    {
                        image.Metadata.Location = objectLocation.Description;
                    }
                }

                if (!image.Metadata.City.HasValue() || overwriteImageProperties)
                {
                    var objectCity = iptcDirectory.Tags.SingleOrDefault(t => t.Type == IptcDirectory.TagCity);
                    if (objectCity != null && objectCity.Description.HasValue())
                    {
                        image.Metadata.City = objectCity.Description;
                    }
                }

                if (!image.Metadata.State.HasValue() || overwriteImageProperties)
                {
                    var objectState = iptcDirectory.Tags.SingleOrDefault(t => t.Type == IptcDirectory.TagProvinceOrState);
                    if (objectState != null && objectState.Description.HasValue())
                    {
                        image.Metadata.State = objectState.Description;
                    }
                }

                if (!image.Metadata.Country.HasValue() || overwriteImageProperties)
                {
                    var objectCountry = iptcDirectory.Tags.SingleOrDefault(t => t.Type == IptcDirectory.TagCountryOrPrimaryLocationName);
                    if (objectCountry != null && objectCountry.Description.HasValue())
                    {
                        image.Metadata.Country = objectCountry.Description;
                    }
                }
            }

            var xmpDirectory = directories.OfType <XmpDirectory>().FirstOrDefault();

            if (xmpDirectory != null)
            {
                // see if there's any tags (subjects in xmp)
                // this will just add tags, so if we're re-processing, we won't lose any manually-entered ones
                // or cause duplications.
                var subjects = xmpDirectory.XmpMeta.Properties.Where(q => q.Path != null && q.Path.StartsWith("dc:subject") && !string.IsNullOrEmpty(q.Value));
                foreach (var subject in subjects)
                {
                    image.TagsCsv = Utilities.AddTagToCsv(image.TagsCsv, subject.Value);
                }

                // sometimes we have no camera info, but sometimes something can be inferred from lens profile info
                var lensProfileFilename = xmpDirectory.XmpMeta.GetPropertyString("http://ns.adobe.com/camera-raw-settings/1.0/", "crs:LensProfileFilename");
                if (!string.IsNullOrEmpty(lensProfileFilename) && string.IsNullOrEmpty(image.Metadata.CameraModel))
                {
                    var camera = lensProfileFilename.Substring(0, lensProfileFilename.IndexOf(" (", StringComparison.Ordinal));
                    image.Metadata.CameraModel = camera;
                }

                var lensProfileName = xmpDirectory.XmpMeta.GetPropertyString("http://ns.adobe.com/camera-raw-settings/1.0/", "crs:LensProfileName");
                if (!string.IsNullOrEmpty(lensProfileName) && string.IsNullOrEmpty(image.Metadata.LensModel))
                {
                    var lens = Regex.Match(lensProfileName, @"\((.*?)\)", RegexOptions.Compiled);
                    if (lens.Success && lens.Value.Contains("(") && lens.Value.Contains(")"))
                    {
                        image.Metadata.LensModel = lens.Value.TrimStart('(').TrimEnd(')');
                    }
                }
            }

            image.Metadata.DateLastProcessed = DateTime.Now;
            stopwatch.Stop();
            log?.Information($"LB.PhotoGalleries.Worker.MetadataUtils.ParseAndAssignImageMetadata() - Processed metadata for {image.Id} in {stopwatch.ElapsedMilliseconds}ms");
        }
예제 #9
0
        private async Task <Result> AddOrReplace(IFormFile file, string fileName, AgentContext agentContext)
        {
            return(await Validate()
                   .Bind(Upload)
                   .Tap(StoreLink));


            async Task <Result <byte[]> > Validate()
            {
                return(await Result.Success()
                       .Ensure(() => file != default, "Could not get the file")
                       .Ensure(() => ImageExtensions.Contains(Path.GetExtension(file?.FileName)?.ToLowerInvariant()),
                               "The file must have extension of a jpeg image")
                       .Map(GetImageBytes)
                       .Bind(ValidateImage));


                async Task <byte[]> GetImageBytes()
                {
                    await using var stream = file.OpenReadStream();
                    using var binaryReader = new BinaryReader(stream);
                    return(binaryReader.ReadBytes((int)file.Length));
                }

                async Task <Result <byte[]> > ValidateImage(byte[] imageBytes)
                {
                    // Here should be checks like aspect ratio, for now only resolution checks
                    var imageInfo = await ImageJob.GetImageInfo(new BytesSource(imageBytes));

                    return(Result.Success()
                           .Ensure(() => imageInfo.ImageWidth >= MinimumWidth && imageInfo.ImageWidth <= MaximumWidth,
                                   $"Image width must be in range from {MinimumWidth} to {MaximumWidth}")
                           .Ensure(() => imageInfo.ImageHeight >= MinimumHeight && imageInfo.ImageHeight <= MaximumHeight,
                                   $"Image height must be in range from {MinimumWidth} to {MaximumWidth}")
                           .Map(() => imageBytes));
                }
            }

            async Task <Result <string> > Upload(byte[] imageBytes)
            {
                await using var stream = new MemoryStream(imageBytes);
                return(await _amazonS3ClientService.Add(_bucketName, GetKey(agentContext.AgencyId, fileName), stream, S3CannedACL.PublicRead));
            }

            async Task StoreLink(string url)
            {
                var oldUploadedImage = await _edoContext.UploadedImages
                                       .SingleOrDefaultAsync(i => i.AgencyId == agentContext.AgentId && i.FileName == fileName);

                if (oldUploadedImage == null)
                {
                    var now = _dateTimeProvider.UtcNow();

                    var newUploadedImage = new UploadedImage
                    {
                        AgencyId = agentContext.AgencyId,
                        Url      = url,
                        FileName = fileName,
                        Created  = now,
                        Updated  = now
                    };

                    _edoContext.Add(newUploadedImage);
                }
                else
                {
                    oldUploadedImage.Updated = _dateTimeProvider.UtcNow();
                    oldUploadedImage.Url     = url;

                    _edoContext.Update(oldUploadedImage);
                }

                await _edoContext.SaveChangesAsync();
            }
        }