91网首页-91网页版-91网在线观看-91网站免费观看-91网站永久视频-91网站在线播放

LOGO OA教程 ERP教程 模切知識交流 PMS教程 CRM教程 開發文檔 其他文檔  
 
網站管理員

WPF 使用GDI+提取圖片主色調并生成Mica材質特效背景

freeflydom
2025年6月5日 10:14 本文熱度 146

先看效果,在淺色模式下:

在深色模式下:

P.S. 此算法只是盡可能地接近Windows Mica效果,并非實際實現;主色調提取算法只能確保在絕大多數情況下適用。

測試項目在Github上開源:

TwilightLemon/MicaImageTest: WPF 使用GDI+提取圖片主色調并生成Mica材質特效背景?

一、簡要原理和設計

1.1 Mica效果

Mica效果是Windows 11的一個新特性,旨在為應用程序提供一種更柔和的背景效果。它通過使用桌面壁紙的顏色和紋理來創建一個靜態的模糊背景效果。一個大致的模擬過程如下:

  1. 根據顏色模式(淺色或深色)來調整圖像對比度
  2. 增加一個白色/黑色的遮罩層
  3. 大半徑 高斯模糊處理

在倉庫代碼中給出了所有組件的實現,如果你想調整效果,可以修改以下幾個值:

1 public static void ApplyMicaEffect(this Bitmap bitmap,bool isDarkmode)
2 {
3     bitmap.AdjustContrast(isDarkmode?-1:-20);//Light Mode通常需要一個更高的對比度
4     bitmap.AddMask(isDarkmode);//添加遮罩層
5     bitmap.ScaleImage(2);//放大圖像(原始圖像一般為500x500)以提高輸出圖像質量
6     var rect = new Rectangle(0, 0, bitmap.Width, bitmap.Height);
7     bitmap.GaussianBlur(ref rect, 80f, false);//按需要調整模糊半徑
8 }

1.2 主色調提取與微調

從原始圖像中提取主色調,主要過程如下:

  1. 像素采樣和顏色量化便于統計
  2. 過濾過黑或過白的顏色值(我們會在調整步驟單獨處理)
  3. 根據HSL的飽和度和亮度來計算權重,
    • 飽和度越高,權重越大
    • 亮度穩定(我們定為0.6),權重越大
  4. 選擇權重最大的顏色均值作為主色調

之后為了適配UI,保證亮度、飽和度適合用于呈現內容,還要對顏色進行微調:

  1. 將顏色轉為HSL空間
  2. 根據顏色模式調節亮度
  3. 分層調整飽和度,一般來說暗色模式的對比度比亮色模式高
  4. 對特定色相區間(紅/綠/藍/黃)進行差異化調整

最后計算焦點顏色(FocusAccentColor)只需要根據顏色模式調整亮度即可。

二、使用方法

將代碼倉庫中的ImageHelper.cs添加到項目,然后在需要的地方調用Bitmap的擴展方法來處理圖像。以下是一個簡單的示例:

首先開啟項目允許使用UnSafe代碼:

  <PropertyGroup>
    <!-- 允許使用UnSafe代碼 -->
    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
  </PropertyGroup>  

導入本地圖像文件,計算主色調、焦點色調并應用Mica效果背景:

 var image=new BitmapImage(new Uri(ImagePath));
 SelectedImg = image;
 var bitmap = image.ToBitmap();
 //major color
 var majorColor = bitmap.GetMajorColor().AdjustColor(IsDarkMode);
 var focusColor = majorColor.ApplyColorMode(IsDarkMode);
 App.Current.Resources["AccentColor"] = new SolidColorBrush(majorColor);
 App.Current.Resources["FocusedAccentColor"] = new SolidColorBrush(focusColor);
 //background
 bitmap.ApplyMicaEffect(IsDarkMode);
 BackgroundImg = bitmap.ToBitmapImage();

其中,SelectedImgBackgroundImg是綁定到UI的BitmapImage類型屬性,IsDarkMode是指示當前顏色模式的布爾值。

三、注意事項

  1. 處理大圖像時可能會導致性能下降,建議使用較小的圖像或在后臺線程中處理。
  2. 如果高斯模糊組件報錯,請確保Nuget包System.Drawing.Common的版本為8.0.1,因為代碼中使用了反射獲取Bitmap內部的句柄。
  3. 你可能需要根據實際情況調整模糊半徑和對比度等參數,以獲得最佳效果。
  4. 庫中實現可能并非最佳寫法,如果有更好的方法可以提交PR或者評論區見。

最后附上ImageHelper.cs的完整代碼:


  1 using System.Drawing;
  2 using System.Drawing.Drawing2D;
  3 using System.Drawing.Imaging;
  4 using System.IO;
  5 using System.Reflection;
  6 using System.Runtime.InteropServices;
  7 using System.Windows.Media.Imaging;
  8 
  9 namespace MicaImageTest;
 10 
 11 public static class ImageHelper
 12 {
 13     #region 處理模糊圖像
 14     [DllImport("gdiplus.dll", SetLastError = true, ExactSpelling = true, CharSet = CharSet.Unicode)]
 15     private static extern int GdipBitmapApplyEffect(IntPtr bitmap, IntPtr effect, ref Rectangle rectOfInterest, bool useAuxData, IntPtr auxData, int auxDataSize);
 16     /// <summary>
 17     /// 獲取對象的私有字段的值
 18     /// </summary>
 19     /// <typeparam name="TResult">字段的類型</typeparam>
 20     /// <param name="obj">要從其中獲取字段值的對象</param>
 21     /// <param name="fieldName">字段的名稱.</param>
 22     /// <returns>字段的值</returns>
 23     /// <exception cref="System.InvalidOperationException">無法找到該字段.</exception>
 24     /// 
 25     internal static TResult GetPrivateField<TResult>(this object obj, string fieldName)
 26     {
 27         if (obj == null) return default(TResult);
 28         Type ltType = obj.GetType();
 29         FieldInfo lfiFieldInfo = ltType.GetField(fieldName, BindingFlags.GetField | BindingFlags.Instance | BindingFlags.NonPublic);
 30         if (lfiFieldInfo != null)
 31             return (TResult)lfiFieldInfo.GetValue(obj);
 32         else
 33             throw new InvalidOperationException(string.Format("Instance field '{0}' could not be located in object of type '{1}'.", fieldName, obj.GetType().FullName));
 34     }
 35 
 36     [StructLayout(LayoutKind.Sequential)]
 37     private struct BlurParameters
 38     {
 39         internal float Radius;
 40         internal bool ExpandEdges;
 41     }
 42     [DllImport("gdiplus.dll", SetLastError = true, ExactSpelling = true, CharSet = CharSet.Unicode)]
 43     private static extern int GdipCreateEffect(Guid guid, out IntPtr effect);
 44     private static Guid BlurEffectGuid = new Guid("{633C80A4-1843-482B-9EF2-BE2834C5FDD4}");
 45     [DllImport("gdiplus.dll", SetLastError = true, ExactSpelling = true, CharSet = CharSet.Unicode)]
 46     private static extern int GdipSetEffectParameters(IntPtr effect, IntPtr parameters, uint size);
 47     public static IntPtr NativeHandle(this Bitmap Bmp)
 48     {
 49         // 通過反射獲取Bitmap的私有字段nativeImage的值,該值為GDI+的內部圖像句柄
 50         //新版(8.0.1)Drawing的Nuget包中字段由 nativeImage變更為_nativeImage
 51         return Bmp.GetPrivateField<IntPtr>("_nativeImage");
 52     }
 53     [DllImport("gdiplus.dll", SetLastError = true, ExactSpelling = true, CharSet = CharSet.Unicode)]
 54     private static extern int GdipDeleteEffect(IntPtr effect);
 55     public static void GaussianBlur(this Bitmap Bmp, ref Rectangle Rect, float Radius = 10, bool ExpandEdge = false)
 56     {
 57         int Result;
 58         IntPtr BlurEffect;
 59         BlurParameters BlurPara;
 60         if ((Radius < 0) || (Radius > 255))
 61         {
 62             throw new ArgumentOutOfRangeException("半徑必須在[0,255]范圍內");
 63         }
 64         BlurPara.Radius = Radius;
 65         BlurPara.ExpandEdges = ExpandEdge;
 66         Result = GdipCreateEffect(BlurEffectGuid, out BlurEffect);
 67         if (Result == 0)
 68         {
 69             IntPtr Handle = Marshal.AllocHGlobal(Marshal.SizeOf(BlurPara));
 70             Marshal.StructureToPtr(BlurPara, Handle, true);
 71             GdipSetEffectParameters(BlurEffect, Handle, (uint)Marshal.SizeOf(BlurPara));
 72             GdipBitmapApplyEffect(Bmp.NativeHandle(), BlurEffect, ref Rect, false, IntPtr.Zero, 0);
 73             // 使用GdipBitmapCreateApplyEffect函數可以不改變原始的圖像,而把模糊的結果寫入到一個新的圖像中
 74             GdipDeleteEffect(BlurEffect);
 75             Marshal.FreeHGlobal(Handle);
 76         }
 77         else
 78         {
 79             throw new ExternalException("不支持的GDI+版本,必須為GDI+1.1及以上版本,且操作系統要求為Win Vista及之后版本.");
 80         }
 81     }
 82     #endregion
 83 
 84     public static System.Windows.Media.Color GetMajorColor(this Bitmap bitmap)
 85     {
 86         int skip = Math.Max(1, Math.Min(bitmap.Width, bitmap.Height) / 100);
 87 
 88         Dictionary<int, ColorInfo> colorMap = [];
 89         int pixelCount = 0;
 90 
 91         for (int h = 0; h < bitmap.Height; h += skip)
 92         {
 93             for (int w = 0; w < bitmap.Width; w += skip)
 94             {
 95                 Color pixel = bitmap.GetPixel(w, h);
 96 
 97                 // 量化顏色 (減少相似顏色的數量)
 98                 int quantizedR = pixel.R / 16 * 16;
 99                 int quantizedG = pixel.G / 16 * 16;
100                 int quantizedB = pixel.B / 16 * 16;
101 
102                 // 排除極端黑白色
103                 int averange = (pixel.R + pixel.G + pixel.B) / 3;
104                 if (averange < 24) continue;
105                 if (averange > 230) continue;
106 
107                 int colorKey = (quantizedR << 16) | (quantizedG << 8) | quantizedB;
108 
109                 if (colorMap.TryGetValue(colorKey, out ColorInfo info))
110                 {
111                     info.Count++;
112                     info.SumR += pixel.R;
113                     info.SumG += pixel.G;
114                     info.SumB += pixel.B;
115                 }
116                 else
117                 {
118                     colorMap[colorKey] = new ColorInfo
119                     {
120                         Count = 1,
121                         SumR = pixel.R,
122                         SumG = pixel.G,
123                         SumB = pixel.B
124                     };
125                 }
126                 pixelCount++;
127             }
128         }
129 
130         if (pixelCount == 0 || colorMap.Count == 0)
131             return System.Windows.Media.Colors.Gray;
132 
133         var weightedColors = colorMap.Values.Select(info =>
134         {
135             float r = info.SumR / (float)info.Count / 255f;
136             float g = info.SumG / (float)info.Count / 255f;
137             float b = info.SumB / (float)info.Count / 255f;
138 
139             // 轉換為HSL來檢查飽和度和亮度
140             RgbToHsl(r, g, b, out float h, out float s, out float l);
141 
142             // 顏色越飽和越有可能是主色調,過亮或過暗的顏色權重降低
143             float weight = info.Count * s * (1 - Math.Abs(l - 0.6f) * 1.8f);
144 
145             return new
146             {
147                 R = info.SumR / info.Count,
148                 G = info.SumG / info.Count,
149                 B = info.SumB / info.Count,
150                 Weight = weight
151             };
152         })
153         .OrderByDescending(c => c.Weight);
154 
155         if (weightedColors.First() is { } dominantColor)
156         {
157             // 取權重最高的顏色
158             return System.Windows.Media.Color.FromRgb(
159                 (byte)dominantColor.R,
160                 (byte)dominantColor.G,
161                 (byte)dominantColor.B);
162         }
163 
164         return System.Windows.Media.Colors.Gray;
165     }
166 
167     private class ColorInfo
168     {
169         public int Count { get; set; }
170         public int SumR { get; set; }
171         public int SumG { get; set; }
172         public int SumB { get; set; }
173     }
174 
175     public static System.Windows.Media.Color AdjustColor(this System.Windows.Media.Color col, bool isDarkMode)
176     {
177         // 轉換為HSL色彩空間,便于調整亮度和飽和度
178         RgbToHsl(col.R / 255f, col.G / 255f, col.B / 255f, out float h, out float s, out float l);
179 
180         bool isNearGrayscale = s < 0.15f; // 判斷是否接近灰度
181 
182         // 1. 基于UI模式進行初步亮度調整
183         if (isDarkMode)
184         {
185             // 在暗色模式下,避免顏色過暗,提高整體亮度
186             if (l < 0.5f)
187                 l = 0.3f + l * 0.5f;
188 
189             if (isNearGrayscale)
190                 l = Math.Max(l, 0.4f); // 確保足夠明亮
191         }
192         else
193         {
194             // 在亮色模式下,避免顏色過亮,降低整體亮度
195             if (l > 0.5f)
196                 l = 0.3f + l * 0.4f;
197 
198             if (isNearGrayscale)
199                 l = Math.Min(l, 0.6f); // 確保不過亮
200         }
201 
202         // 2. 調整飽和度
203         if (!isNearGrayscale)
204         {
205             if (s > 0.7f)
206             {
207                 // 高飽和度降低,但是暗色模式需要更鮮明的顏色
208                 s = isDarkMode ? 0.7f - (s - 0.7f) * 0.2f : 0.65f - (s - 0.7f) * 0.4f;
209             }
210             else if (s > 0.4f)
211             {
212                 // 中等飽和度微調
213                 s = isDarkMode ? s * 0.85f : s * 0.75f;
214             }
215             else if (s > 0.1f) // 低飽和度但不是接近灰度
216             {
217                 // 低飽和度增強,尤其在暗色模式下
218                 s = isDarkMode ? Math.Min(0.5f, s * 1.5f) : Math.Min(0.4f, s * 1.3f);
219             }
220         }
221 
222         // 3. 特殊色相區域的處理
223         if (!isNearGrayscale) // 僅處理有明顯色相的顏色
224         {
225             // 紅色區域 (0-30° 或 330-360°)
226             if ((h <= 0.08f) || (h >= 0.92f))
227             {
228                 if (isDarkMode)
229                 {
230                     // 暗色模式下紅色需要更高飽和度和亮度
231                     s = Math.Min(0.7f, s * 1.1f);
232                     l = Math.Min(0.8f, l * 1.15f);
233                 }
234                 else
235                 {
236                     // 亮色模式下紅色降低飽和度,避免刺眼
237                     s *= 0.8f;
238                     l = Math.Max(0.4f, l * 0.9f);
239                 }
240             }
241             // 綠色區域 (90-150°)
242             else if (h >= 0.25f && h <= 0.42f)
243             {
244                 if (isDarkMode)
245                 {
246                     // 暗色模式下綠色提高亮度,降低飽和度,避免熒光感
247                     s *= 0.85f;
248                     l = Math.Min(0.7f, l * 1.2f);
249                 }
250                 else
251                 {
252                     // 亮色模式下綠色降低飽和度更多
253                     s *= 0.75f;
254                 }
255             }
256             // 藍色區域 (210-270°)
257             else if (h >= 0.58f && h <= 0.75f)
258             {
259                 if (isDarkMode)
260                 {
261                     // 暗色模式下藍色提高亮度和飽和度
262                     s = Math.Min(0.85f, s * 1.2f);
263                     l = Math.Min(0.7f, l * 1.25f);
264                 }
265                 else
266                 {
267                     // 亮色模式下藍色保持中等飽和度
268                     s = Math.Min(0.7f, Math.Max(0.4f, s));
269                 }
270             }
271             // 黃色區域 (30-90°)
272             else if (h > 0.08f && h < 0.25f)
273             {
274                 if (isDarkMode)
275                 {
276                     // 暗色模式下黃色需要降低飽和度,提高亮度
277                     s *= 0.8f;
278                     l = Math.Min(0.75f, l * 1.2f);
279                 }
280                 else
281                 {
282                     // 亮色模式下黃色大幅降低飽和度
283                     s *= 0.7f;
284                     l = Math.Max(0.5f, l * 0.9f);
285                 }
286             }
287         }
288 
289 
290 
291         // 5. 最終亮度修正 - 確保在各種UI模式下都有足夠的對比度
292         if (isDarkMode && l < 0.3f) l = 0.3f; // 暗色模式下確保最小亮度
293         if (!isDarkMode && l > 0.7f) l = 0.7f; // 亮色模式下確保最大亮度
294 
295         // 轉換回RGB
296         HslToRgb(h, s, l, out float r, out float g, out float b);
297 
298         // 確保RGB值在有效范圍內
299         byte R = (byte)Math.Max(0, Math.Min(255, r * 255));
300         byte G = (byte)Math.Max(0, Math.Min(255, g * 255));
301         byte B = (byte)Math.Max(0, Math.Min(255, b * 255));
302 
303         return System.Windows.Media.Color.FromRgb(R, G, B);
304     }
305     public static System.Windows.Media.Color ApplyColorMode(this System.Windows.Media.Color color,bool isDarkMode)
306     {
307         RgbToHsl(color.R/255f,color.G/255f, color.B/255f,out float h, out float s, out float l);
308         if (isDarkMode)
309             l = Math.Max(0.05f, l - 0.1f);
310         else
311             l = Math.Min(0.95f, l + 0.1f);
312 
313         HslToRgb(h, s, l, out float r, out float g, out float b);
314         return System.Windows.Media.Color.FromRgb((byte)(r * 255), (byte)(g * 255), (byte)(b * 255));
315     }
316 
317     private static void RgbToHsl(float r, float g, float b, out float h, out float s, out float l)
318     {
319         float max = Math.Max(r, Math.Max(g, b));
320         float min = Math.Min(r, Math.Min(g, b));
321 
322         // 計算亮度
323         l = (max + min) / 2.0f;
324 
325         // 默認值初始化
326         h = 0;
327         s = 0;
328 
329         if (max == min)
330         {
331             // 無色調 (灰色)
332             return;
333         }
334 
335         float d = max - min;
336 
337         // 計算飽和度
338         s = l > 0.5f ? d / (2.0f - max - min) : d / (max + min);
339 
340         // 計算色相
341         if (max == r)
342         {
343             h = (g - b) / d + (g < b ? 6.0f : 0.0f);
344         }
345         else if (max == g)
346         {
347             h = (b - r) / d + 2.0f;
348         }
349         else // max == b
350         {
351             h = (r - g) / d + 4.0f;
352         }
353 
354         h /= 6.0f;
355 
356         // 確保h在[0,1]范圍內
357         h = Math.Max(0, Math.Min(1, h));
358     }
359 
360     private static void HslToRgb(float h, float s, float l, out float r, out float g, out float b)
361     {
362         // 確保h在[0,1]范圍內
363         h = ((h % 1.0f) + 1.0f) % 1.0f;
364 
365         // 確保s和l在[0,1]范圍內
366         s = Math.Max(0, Math.Min(1, s));
367         l = Math.Max(0, Math.Min(1, l));
368 
369         if (s == 0.0f)
370         {
371             // 灰度顏色
372             r = g = b = l;
373             return;
374         }
375 
376         float q = l < 0.5f ? l * (1.0f + s) : l + s - l * s;
377         float p = 2.0f * l - q;
378 
379         r = HueToRgb(p, q, h + 1.0f / 3.0f);
380         g = HueToRgb(p, q, h);
381         b = HueToRgb(p, q, h - 1.0f / 3.0f);
382     }
383 
384     private static float HueToRgb(float p, float q, float t)
385     {
386         // 確保t在[0,1]范圍內
387         t = ((t % 1.0f) + 1.0f) % 1.0f;
388 
389         if (t < 1.0f / 6.0f)
390             return p + (q - p) * 6.0f * t;
391         if (t < 0.5f)
392             return q;
393         if (t < 2.0f / 3.0f)
394             return p + (q - p) * (2.0f / 3.0f - t) * 6.0f;
395         return p;
396     }
397     public static BitmapImage ToBitmapImage(this Bitmap Bmp)
398     {
399         BitmapImage BmpImage = new();
400         using (MemoryStream lmemStream = new())
401         {
402             Bmp.Save(lmemStream, ImageFormat.Png);
403             BmpImage.BeginInit();
404             BmpImage.StreamSource = new MemoryStream(lmemStream.ToArray());
405             BmpImage.EndInit();
406         }
407         return BmpImage;
408     }
409 
410     public static Bitmap ToBitmap(this BitmapImage img){
411         using MemoryStream outStream = new();
412         BitmapEncoder enc = new PngBitmapEncoder();
413         enc.Frames.Add(BitmapFrame.Create(img));
414         enc.Save(outStream);
415         return new Bitmap(outStream);
416     }
417 
418     public static void AddMask(this Bitmap bitmap,bool darkmode)
419     {
420         var color1 = darkmode ? Color.FromArgb(150, 0, 0, 0) : Color.FromArgb(160, 255, 255, 255);
421         var color2 = darkmode ? Color.FromArgb(180, 0, 0, 0) : Color.FromArgb(200, 255, 255, 255);
422         using Graphics g = Graphics.FromImage(bitmap);
423         using LinearGradientBrush brush = new(
424             new Rectangle(0, 0, bitmap.Width, bitmap.Height),
425             color1,
426             color2,
427             LinearGradientMode.Vertical);
428         g.FillRectangle(brush, new Rectangle(0, 0, bitmap.Width, bitmap.Height));
429     }
430     public static void AdjustContrast(this Bitmap bitmap, float contrast)
431     {
432         contrast = (100.0f + contrast) / 100.0f;
433         contrast *= contrast;
434 
435         BitmapData data = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height),
436             ImageLockMode.ReadWrite, bitmap.PixelFormat);
437 
438         int width = bitmap.Width;
439         int height = bitmap.Height;
440 
441         unsafe
442         {
443             for (int y = 0; y < height; y++)
444             {
445                 byte* row = (byte*)data.Scan0 + (y * data.Stride);
446                 for (int x = 0; x < width; x++)
447                 {
448                     int idx = x * 3;
449 
450                     float blue = row[idx] / 255.0f;
451                     float green = row[idx + 1] / 255.0f;
452                     float red = row[idx + 2] / 255.0f;
453 
454                     // 轉換為HSL
455                     RgbToHsl(red, green, blue, out float h, out float s, out float l);
456 
457                     // 調整亮度以增加對比度
458                     l = (((l - 0.5f) * contrast) + 0.5f);
459 
460                     // 轉換回RGB
461                     HslToRgb(h, s, l, out red, out green, out blue);
462 
463                     row[idx] = (byte)Math.Max(0, Math.Min(255, blue * 255.0f));
464                     row[idx + 1] = (byte)Math.Max(0, Math.Min(255, green * 255.0f));
465                     row[idx + 2] = (byte)Math.Max(0, Math.Min(255, red * 255.0f));
466                 }
467             }
468         }
469 
470         bitmap.UnlockBits(data);
471     }
472 
473     public static void ScaleImage(this Bitmap bitmap, double scale)
474     {
475         // 計算新的尺寸
476         int newWidth = (int)(bitmap.Width * scale);
477         int newHeight = (int)(bitmap.Height * scale);
478 
479         // 創建目標位圖
480         Bitmap newBitmap = new Bitmap(newWidth, newHeight, bitmap.PixelFormat);
481 
482         // 設置高質量繪圖參數
483         using (Graphics graphics = Graphics.FromImage(newBitmap))
484         {
485             graphics.CompositingQuality = CompositingQuality.HighQuality;
486             graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
487             graphics.SmoothingMode = SmoothingMode.HighQuality;
488             graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
489 
490             // 繪制縮放后的圖像
491             graphics.DrawImage(bitmap,
492                 new Rectangle(0, 0, newWidth, newHeight),
493                 new Rectangle(0, 0, bitmap.Width, bitmap.Height),
494                 GraphicsUnit.Pixel);
495         }
496         bitmap = newBitmap;
497     }
498 
499     public static void ApplyMicaEffect(this Bitmap bitmap,bool isDarkmode)
500     {
501         bitmap.AdjustContrast(isDarkmode?-1:-20);
502         bitmap.AddMask(isDarkmode);
503         bitmap.ScaleImage(2);
504         var rect = new Rectangle(0, 0, bitmap.Width, bitmap.Height);
505         bitmap.GaussianBlur(ref rect, 80f, false);
506     }
507 }

 


該文章在 2025/6/5 10:14:17 編輯過
關鍵字查詢
相關文章
正在查詢...
點晴ERP是一款針對中小制造業的專業生產管理軟件系統,系統成熟度和易用性得到了國內大量中小企業的青睞。
點晴PMS碼頭管理系統主要針對港口碼頭集裝箱與散貨日常運作、調度、堆場、車隊、財務費用、相關報表等業務管理,結合碼頭的業務特點,圍繞調度、堆場作業而開發的。集技術的先進性、管理的有效性于一體,是物流碼頭及其他港口類企業的高效ERP管理信息系統。
點晴WMS倉儲管理系統提供了貨物產品管理,銷售管理,采購管理,倉儲管理,倉庫管理,保質期管理,貨位管理,庫位管理,生產管理,WMS管理系統,標簽打印,條形碼,二維碼管理,批號管理軟件。
點晴免費OA是一款軟件和通用服務都免費,不限功能、不限時間、不限用戶的免費OA協同辦公管理系統。
Copyright 2010-2025 ClickSun All Rights Reserved

主站蜘蛛池模板: 欧美亚洲日本国 | 日本高清在线一 | 国产精品福利社 | 果冻影视 | 91丨九 | 日P网站在线观看 | 日本在线网 | 国产成年人视频免费 | 国产精品免费小视频 | 日本一级婬片a | 国产在线愉拍视频 | 成人h网站在线 | 91国语自产拍在线 | 91区国产精品 | 日本伦理 | 伦理片在线观看 | 乱码一二区在线亚洲 | 91精品国产电影 | 区二区在线播放 | 精品在线观看 | 91小视频网站 | 欧洲乱码伦视频免费 | 福利导航精品 | 国产熟女高| 欧美在线+在线播放 | 成人97视频 | 国产伦一区二区三 | 国产香蕉人人干干 | 国产精品自拍真实 | 成人影片麻 | 乱伦自拍影视三级 | 人人97人人干| 亚洲无码中文字幕在线观看 | 91福利页面 | 国产亚洲精品导航 | 午夜在线亚洲男 | 国产精品三级在线 | 欧美日韩精品乱国产 | 91精品国产精品 | 国产高清亚 | 国产午夜激情视频 |