color-analysis.service.ts 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798
  1. import { Injectable } from '@angular/core';
  2. import { HttpClient } from '@angular/common/http';
  3. import { Observable, BehaviorSubject, throwError, forkJoin, of } from 'rxjs';
  4. import { catchError, map } from 'rxjs/operators';
  5. import { FormAnalysisService } from './form-analysis.service';
  6. import { TextureAnalysisService } from './texture-analysis.service';
  7. import { PatternAnalysisService } from './pattern-analysis.service';
  8. import { LightingAnalysisService } from './lighting-analysis.service';
  9. export interface UploadedFile {
  10. id: string;
  11. name: string;
  12. url: string;
  13. size?: number;
  14. type?: string;
  15. preview?: string;
  16. }
  17. export interface ColorAnalysisResult {
  18. colors: Array<{
  19. hex: string;
  20. rgb: { r: number; g: number; b: number };
  21. percentage: number;
  22. name?: string; // 添加可选的name属性
  23. }>;
  24. originalImage: string;
  25. mosaicImage: string;
  26. reportPath: string;
  27. // 新增增强分析结果
  28. enhancedAnalysis?: EnhancedColorAnalysis;
  29. // 新增其他分析结果
  30. formAnalysis?: any;
  31. textureAnalysis?: any;
  32. patternAnalysis?: any;
  33. lightingAnalysis?: any;
  34. }
  35. // 新增:增强色彩分析结果接口
  36. export interface EnhancedColorAnalysis {
  37. colorWheel: ColorWheelData;
  38. colorHarmony: ColorHarmonyAnalysis;
  39. colorTemperature: ColorTemperatureAnalysis;
  40. colorPsychology: ColorPsychologyAnalysis;
  41. }
  42. // 色轮数据
  43. export interface ColorWheelData {
  44. dominantHue: number; // 主色调角度 (0-360)
  45. saturationRange: { min: number; max: number }; // 饱和度范围
  46. brightnessRange: { min: number; max: number }; // 亮度范围
  47. colorDistribution: Array<{
  48. hue: number;
  49. saturation: number;
  50. brightness: number;
  51. percentage: number;
  52. }>;
  53. }
  54. // 色彩和谐度分析
  55. export interface ColorHarmonyAnalysis {
  56. harmonyType: 'monochromatic' | 'analogous' | 'complementary' | 'triadic' | 'tetradic' | 'split-complementary';
  57. harmonyScore: number; // 0-100
  58. suggestions: string[];
  59. relationships: Array<{
  60. color1: string;
  61. color2: string;
  62. relationship: string;
  63. strength: number;
  64. }>;
  65. }
  66. // 色温详细分析
  67. export interface ColorTemperatureAnalysis {
  68. averageTemperature: number; // 开尔文值
  69. temperatureRange: { min: number; max: number };
  70. warmCoolBalance: number; // -100(冷) 到 100(暖)
  71. temperatureDescription: string;
  72. lightingRecommendations: string[];
  73. }
  74. // 色彩心理学分析
  75. export interface ColorPsychologyAnalysis {
  76. mood: string; // 整体情绪
  77. atmosphere: string; // 氛围描述
  78. psychologicalEffects: string[];
  79. suitableSpaces: string[]; // 适合的空间类型
  80. emotionalImpact: {
  81. energy: number; // 0-100
  82. warmth: number; // 0-100
  83. sophistication: number; // 0-100
  84. comfort: number; // 0-100
  85. };
  86. }
  87. export interface AnalysisProgress {
  88. stage: 'preparing' | 'processing' | 'extracting' | 'generating' | 'completed' | 'error';
  89. message: string;
  90. progress: number;
  91. }
  92. @Injectable({
  93. providedIn: 'root'
  94. })
  95. export class ColorAnalysisService {
  96. private readonly API_BASE = '/api/color-analysis';
  97. private analysisProgress$ = new BehaviorSubject<AnalysisProgress>({
  98. stage: 'preparing',
  99. message: '准备分析...',
  100. progress: 0
  101. });
  102. constructor(
  103. private http: HttpClient,
  104. private formAnalysisService: FormAnalysisService,
  105. private textureAnalysisService: TextureAnalysisService,
  106. private patternAnalysisService: PatternAnalysisService,
  107. private lightingAnalysisService: LightingAnalysisService
  108. ) {}
  109. /**
  110. * 获取分析进度
  111. */
  112. getAnalysisProgress(): Observable<AnalysisProgress> {
  113. return this.analysisProgress$.asObservable();
  114. }
  115. /**
  116. * 分析图片颜色
  117. * @param imageFile 图片文件
  118. * @param options 分析选项
  119. */
  120. analyzeImageColors(imageFile: File, options?: {
  121. mosaicSize?: number;
  122. maxColors?: number;
  123. }): Observable<ColorAnalysisResult> {
  124. const formData = new FormData();
  125. formData.append('image', imageFile);
  126. if (options?.mosaicSize) {
  127. formData.append('mosaicSize', options.mosaicSize.toString());
  128. }
  129. if (options?.maxColors) {
  130. formData.append('maxColors', options.maxColors.toString());
  131. }
  132. return this.http.post<any>(`${this.API_BASE}/analyze`, formData).pipe(
  133. map((response: any) => {
  134. if (response && response.success) {
  135. return this.parseAnalysisResult(response.data);
  136. }
  137. throw new Error('分析结果无效');
  138. }),
  139. catchError(error => {
  140. console.error('颜色分析失败:', error);
  141. return throwError(() => new Error('颜色分析服务暂时不可用'));
  142. })
  143. );
  144. }
  145. /**
  146. * 分析上传的图片文件
  147. * @param file 上传的文件信息
  148. */
  149. analyzeImage(file: UploadedFile): Observable<ColorAnalysisResult> {
  150. // 暂时使用模拟分析,避免API 404错误
  151. console.log('使用模拟分析处理文件:', file.name);
  152. // 创建一个File对象用于模拟分析
  153. return new Observable(observer => {
  154. // 如果有预览URL,尝试获取文件
  155. if (file.preview || file.url) {
  156. fetch(file.preview || file.url)
  157. .then(response => response.blob())
  158. .then(blob => {
  159. const mockFile = new File([blob], file.name, { type: file.type || 'image/jpeg' });
  160. this.simulateAnalysis(mockFile).subscribe({
  161. next: (result) => observer.next(result),
  162. error: (error) => observer.error(error),
  163. complete: () => observer.complete()
  164. });
  165. })
  166. .catch(error => {
  167. console.warn('无法获取文件内容,使用默认模拟数据:', error);
  168. // 如果无法获取文件,直接返回模拟结果
  169. this.getDefaultMockResult(file).subscribe({
  170. next: (result) => observer.next(result),
  171. error: (error) => observer.error(error),
  172. complete: () => observer.complete()
  173. });
  174. });
  175. } else {
  176. // 没有文件URL,直接返回模拟结果
  177. this.getDefaultMockResult(file).subscribe({
  178. next: (result) => observer.next(result),
  179. error: (error) => observer.error(error),
  180. complete: () => observer.complete()
  181. });
  182. }
  183. });
  184. }
  185. /**
  186. * 获取默认模拟结果
  187. */
  188. private getDefaultMockResult(file: UploadedFile): Observable<ColorAnalysisResult> {
  189. return new Observable(observer => {
  190. this.updateProgress('processing', '开始分析...', 10);
  191. setTimeout(() => {
  192. this.updateProgress('extracting', '提取颜色信息...', 50);
  193. setTimeout(() => {
  194. this.updateProgress('generating', '生成分析报告...', 80);
  195. setTimeout(() => {
  196. const mockResult: ColorAnalysisResult = {
  197. colors: [
  198. { hex: '#FF6B6B', rgb: { r: 255, g: 107, b: 107 }, percentage: 25.5, name: '珊瑚红' },
  199. { hex: '#4ECDC4', rgb: { r: 78, g: 205, b: 196 }, percentage: 18.3, name: '青绿色' },
  200. { hex: '#45B7D1', rgb: { r: 69, g: 183, b: 209 }, percentage: 15.7, name: '天蓝色' },
  201. { hex: '#96CEB4', rgb: { r: 150, g: 206, b: 180 }, percentage: 12.1, name: '薄荷绿' },
  202. { hex: '#FFEAA7', rgb: { r: 255, g: 234, b: 167 }, percentage: 10.8, name: '柠檬黄' },
  203. { hex: '#DDA0DD', rgb: { r: 221, g: 160, b: 221 }, percentage: 8.9, name: '紫罗兰' },
  204. { hex: '#98D8C8', rgb: { r: 152, g: 216, b: 200 }, percentage: 8.7, name: '海泡石绿' }
  205. ],
  206. originalImage: file.preview || file.url || document.baseURI+'/assets/images/placeholder.jpg',
  207. mosaicImage: file.preview || file.url || document.baseURI+'/assets/images/placeholder.jpg',
  208. reportPath: '/mock-report.html',
  209. enhancedAnalysis: this.performEnhancedColorAnalysis([
  210. { hex: '#FF6B6B', rgb: { r: 255, g: 107, b: 107 }, percentage: 25.5 },
  211. { hex: '#4ECDC4', rgb: { r: 78, g: 205, b: 196 }, percentage: 18.3 },
  212. { hex: '#45B7D1', rgb: { r: 69, g: 183, b: 209 }, percentage: 15.7 },
  213. { hex: '#96CEB4', rgb: { r: 150, g: 206, b: 180 }, percentage: 12.1 },
  214. { hex: '#FFEAA7', rgb: { r: 255, g: 234, b: 167 }, percentage: 10.8 }
  215. ])
  216. };
  217. this.updateProgress('completed', '分析完成', 100);
  218. observer.next(mockResult);
  219. observer.complete();
  220. }, 300);
  221. }, 400);
  222. }, 300);
  223. });
  224. }
  225. /**
  226. * 获取分析报告
  227. * @param reportId 报告ID
  228. */
  229. getAnalysisReport(reportId: string): Observable<string> {
  230. return this.http.get(`${this.API_BASE}/report/${reportId}`, {
  231. responseType: 'text'
  232. });
  233. }
  234. /**
  235. * 批量分析多个图片
  236. * @param imageFiles 图片文件数组
  237. */
  238. analyzeBatchImages(imageFiles: File[]): Observable<ColorAnalysisResult[]> {
  239. this.updateProgress('preparing', '准备批量分析...', 0);
  240. const formData = new FormData();
  241. imageFiles.forEach((file, index) => {
  242. formData.append(`images`, file);
  243. });
  244. return this.http.post<any>(`${this.API_BASE}/analyze-batch`, formData).pipe(
  245. map(response => {
  246. this.updateProgress('completed', '批量分析完成', 100);
  247. return response.results.map((result: any) => this.parseAnalysisResult(result));
  248. }),
  249. catchError(error => {
  250. this.updateProgress('error', '批量分析失败: ' + (error.message || '未知错误'), 0);
  251. return throwError(() => error);
  252. })
  253. );
  254. }
  255. /**
  256. * 检查color-get服务状态
  257. */
  258. checkServiceStatus(): Observable<boolean> {
  259. return this.http.get<{ status: string }>(`${this.API_BASE}/status`).pipe(
  260. map(response => response.status === 'ready'),
  261. catchError(() => throwError(() => new Error('颜色分析服务不可用')))
  262. );
  263. }
  264. /**
  265. * 增强色彩分析 - 包含色轮、和谐度、色温、心理学分析
  266. * @param colors 基础颜色分析结果
  267. */
  268. performEnhancedColorAnalysis(colors: Array<{hex: string; rgb: {r: number; g: number; b: number}; percentage: number}>): EnhancedColorAnalysis {
  269. return {
  270. colorWheel: this.analyzeColorWheel(colors),
  271. colorHarmony: this.analyzeColorHarmony(colors),
  272. colorTemperature: this.analyzeColorTemperature(colors),
  273. colorPsychology: this.analyzeColorPsychology(colors)
  274. };
  275. }
  276. /**
  277. * 色轮分析
  278. */
  279. private analyzeColorWheel(colors: Array<{hex: string; rgb: {r: number; g: number; b: number}; percentage: number}>): ColorWheelData {
  280. const colorDistribution = colors.map(color => {
  281. const hsl = this.rgbToHsl(color.rgb.r, color.rgb.g, color.rgb.b);
  282. return {
  283. hue: hsl.h,
  284. saturation: hsl.s,
  285. brightness: hsl.l,
  286. percentage: color.percentage
  287. };
  288. });
  289. const dominantColor = colorDistribution.reduce((prev, current) =>
  290. prev.percentage > current.percentage ? prev : current
  291. );
  292. const saturationValues = colorDistribution.map(c => c.saturation);
  293. const brightnessValues = colorDistribution.map(c => c.brightness);
  294. return {
  295. dominantHue: dominantColor.hue,
  296. saturationRange: {
  297. min: Math.min(...saturationValues),
  298. max: Math.max(...saturationValues)
  299. },
  300. brightnessRange: {
  301. min: Math.min(...brightnessValues),
  302. max: Math.max(...brightnessValues)
  303. },
  304. colorDistribution
  305. };
  306. }
  307. /**
  308. * 色彩和谐度分析
  309. */
  310. private analyzeColorHarmony(colors: Array<{hex: string; rgb: {r: number; g: number; b: number}; percentage: number}>): ColorHarmonyAnalysis {
  311. const hues = colors.map(color => {
  312. const hsl = this.rgbToHsl(color.rgb.r, color.rgb.g, color.rgb.b);
  313. return { hue: hsl.h, hex: color.hex, percentage: color.percentage };
  314. });
  315. // 分析色彩关系
  316. const relationships:any = [];
  317. for (let i = 0; i < hues.length; i++) {
  318. for (let j = i + 1; j < hues.length; j++) {
  319. const hueDiff = Math.abs(hues[i].hue - hues[j].hue);
  320. const minDiff = Math.min(hueDiff, 360 - hueDiff);
  321. let relationship = '';
  322. let strength = 0;
  323. if (minDiff < 30) {
  324. relationship = '相似色';
  325. strength = 90 - minDiff;
  326. } else if (minDiff > 150 && minDiff < 210) {
  327. relationship = '互补色';
  328. strength = 100 - Math.abs(minDiff - 180);
  329. } else if (minDiff > 110 && minDiff < 130) {
  330. relationship = '三角色';
  331. strength = 100 - Math.abs(minDiff - 120);
  332. }
  333. if (relationship) {
  334. relationships.push({
  335. color1: hues[i].hex,
  336. color2: hues[j].hex,
  337. relationship,
  338. strength
  339. });
  340. }
  341. }
  342. }
  343. // 确定和谐类型
  344. let harmonyType: ColorHarmonyAnalysis['harmonyType'] = 'monochromatic';
  345. let harmonyScore = 60;
  346. if (relationships.some(r => r.relationship === '互补色' && r.strength > 80)) {
  347. harmonyType = 'complementary';
  348. harmonyScore = 85;
  349. } else if (relationships.filter(r => r.relationship === '相似色').length >= 2) {
  350. harmonyType = 'analogous';
  351. harmonyScore = 75;
  352. } else if (relationships.some(r => r.relationship === '三角色')) {
  353. harmonyType = 'triadic';
  354. harmonyScore = 80;
  355. }
  356. return {
  357. harmonyType,
  358. harmonyScore,
  359. relationships,
  360. suggestions: this.generateHarmonySuggestions(harmonyType, harmonyScore)
  361. };
  362. }
  363. /**
  364. * 色温分析
  365. */
  366. private analyzeColorTemperature(colors: Array<{hex: string; rgb: {r: number; g: number; b: number}; percentage: number}>): ColorTemperatureAnalysis {
  367. const temperatures = colors.map(color => {
  368. // 简化的色温计算
  369. const temp = this.calculateColorTemperature(color.rgb);
  370. return { temperature: temp, percentage: color.percentage };
  371. });
  372. const weightedAverage = temperatures.reduce((sum, t) => sum + t.temperature * t.percentage, 0) / 100;
  373. const tempValues = temperatures.map(t => t.temperature);
  374. const warmCoolBalance = this.calculateWarmCoolBalance(colors);
  375. return {
  376. averageTemperature: Math.round(weightedAverage),
  377. temperatureRange: {
  378. min: Math.min(...tempValues),
  379. max: Math.max(...tempValues)
  380. },
  381. warmCoolBalance,
  382. temperatureDescription: this.getTemperatureDescription(weightedAverage),
  383. lightingRecommendations: this.generateLightingRecommendations(weightedAverage, warmCoolBalance)
  384. };
  385. }
  386. /**
  387. * 色彩心理学分析
  388. */
  389. private analyzeColorPsychology(colors: Array<{hex: string; rgb: {r: number; g: number; b: number}; percentage: number}>): ColorPsychologyAnalysis {
  390. const psychologyData = colors.map(color => {
  391. const psychology = this.getColorPsychology(color.hex);
  392. return { ...psychology, percentage: color.percentage };
  393. });
  394. // 计算加权平均情感影响
  395. const emotionalImpact = {
  396. energy: Math.round(psychologyData.reduce((sum, p) => sum + p.energy * p.percentage, 0) / 100),
  397. warmth: Math.round(psychologyData.reduce((sum, p) => sum + p.warmth * p.percentage, 0) / 100),
  398. sophistication: Math.round(psychologyData.reduce((sum, p) => sum + p.sophistication * p.percentage, 0) / 100),
  399. comfort: Math.round(psychologyData.reduce((sum, p) => sum + p.comfort * p.percentage, 0) / 100)
  400. };
  401. return {
  402. mood: this.determineMood(emotionalImpact),
  403. atmosphere: this.determineAtmosphere(emotionalImpact),
  404. psychologicalEffects: this.aggregatePsychologicalEffects(psychologyData),
  405. suitableSpaces: this.determineSuitableSpaces(emotionalImpact),
  406. emotionalImpact
  407. };
  408. }
  409. // 辅助方法
  410. private rgbToHsl(r: number, g: number, b: number): {h: number, s: number, l: number} {
  411. r /= 255; g /= 255; b /= 255;
  412. const max = Math.max(r, g, b), min = Math.min(r, g, b);
  413. let h = 0, s = 0, l = (max + min) / 2;
  414. if (max !== min) {
  415. const d = max - min;
  416. s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
  417. switch (max) {
  418. case r: h = (g - b) / d + (g < b ? 6 : 0); break;
  419. case g: h = (b - r) / d + 2; break;
  420. case b: h = (r - g) / d + 4; break;
  421. }
  422. h /= 6;
  423. }
  424. return { h: h * 360, s: s * 100, l: l * 100 };
  425. }
  426. private calculateColorTemperature(rgb: {r: number, g: number, b: number}): number {
  427. // 简化的色温计算公式
  428. const ratio = rgb.b / (rgb.r + rgb.g + rgb.b);
  429. return Math.round(2000 + ratio * 4000); // 2000K-6000K范围
  430. }
  431. private calculateWarmCoolBalance(colors: Array<{hex: string; rgb: {r: number; g: number; b: number}; percentage: number}>): number {
  432. let warmScore = 0;
  433. colors.forEach(color => {
  434. const { r, g, b } = color.rgb;
  435. const warmness = (r - b) / 255 * 100; // 红色减蓝色的比例
  436. warmScore += warmness * color.percentage / 100;
  437. });
  438. return Math.max(-100, Math.min(100, warmScore));
  439. }
  440. private getTemperatureDescription(temp: number): string {
  441. if (temp < 3000) return '暖色调';
  442. if (temp < 4000) return '中性偏暖';
  443. if (temp < 5000) return '中性色调';
  444. if (temp < 6000) return '中性偏冷';
  445. return '冷色调';
  446. }
  447. private generateLightingRecommendations(temp: number, balance: number): string[] {
  448. const recommendations:any = [];
  449. if (temp < 3500) {
  450. recommendations.push('适合使用暖白光照明(2700K-3000K)');
  451. recommendations.push('营造温馨舒适氛围');
  452. } else if (temp > 5000) {
  453. recommendations.push('适合使用冷白光照明(5000K-6500K)');
  454. recommendations.push('营造清爽现代的感觉');
  455. } else {
  456. recommendations.push('适合使用中性白光照明(4000K-4500K)');
  457. recommendations.push('平衡温暖与清爽的感觉');
  458. }
  459. return recommendations;
  460. }
  461. private generateHarmonySuggestions(harmonyType: string, score: number): string[] {
  462. const suggestions:any = [];
  463. if (score < 70) {
  464. suggestions.push('考虑调整色彩比例以提高和谐度');
  465. suggestions.push('可以添加中性色作为过渡');
  466. }
  467. switch (harmonyType) {
  468. case 'complementary':
  469. suggestions.push('互补色搭配,建议一主一辅的比例');
  470. break;
  471. case 'analogous':
  472. suggestions.push('相似色搭配,可以添加少量对比色增加活力');
  473. break;
  474. case 'triadic':
  475. suggestions.push('三角色搭配,注意控制各色彩的饱和度');
  476. break;
  477. }
  478. return suggestions;
  479. }
  480. private getColorPsychology(hex: string): {energy: number, warmth: number, sophistication: number, comfort: number} {
  481. // 基于色相的心理学属性(简化版)
  482. const rgb = this.hexToRgb(hex);
  483. if (!rgb) return { energy: 50, warmth: 50, sophistication: 50, comfort: 50 };
  484. const hsl = this.rgbToHsl(rgb.r, rgb.g, rgb.b);
  485. const hue = hsl.h;
  486. // 根据色相确定心理属性
  487. if (hue >= 0 && hue < 60) { // 红-橙
  488. return { energy: 85, warmth: 90, sophistication: 60, comfort: 70 };
  489. } else if (hue >= 60 && hue < 120) { // 黄-绿
  490. return { energy: 75, warmth: 70, sophistication: 50, comfort: 80 };
  491. } else if (hue >= 120 && hue < 180) { // 绿-青
  492. return { energy: 45, warmth: 30, sophistication: 70, comfort: 85 };
  493. } else if (hue >= 180 && hue < 240) { // 青-蓝
  494. return { energy: 35, warmth: 20, sophistication: 85, comfort: 75 };
  495. } else if (hue >= 240 && hue < 300) { // 蓝-紫
  496. return { energy: 55, warmth: 40, sophistication: 90, comfort: 65 };
  497. } else { // 紫-红
  498. return { energy: 70, warmth: 60, sophistication: 80, comfort: 60 };
  499. }
  500. }
  501. private hexToRgb(hex: string): {r: number, g: number, b: number} | null {
  502. const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
  503. return result ? {
  504. r: parseInt(result[1], 16),
  505. g: parseInt(result[2], 16),
  506. b: parseInt(result[3], 16)
  507. } : null;
  508. }
  509. private determineMood(impact: {energy: number, warmth: number, sophistication: number, comfort: number}): string {
  510. if (impact.energy > 70 && impact.warmth > 70) return '活力温暖';
  511. if (impact.sophistication > 80) return '优雅精致';
  512. if (impact.comfort > 80) return '舒适宁静';
  513. if (impact.energy > 70) return '充满活力';
  514. return '平和稳定';
  515. }
  516. private determineAtmosphere(impact: {energy: number, warmth: number, sophistication: number, comfort: number}): string {
  517. if (impact.warmth > 70 && impact.comfort > 70) return '温馨舒适的家居氛围';
  518. if (impact.sophistication > 80 && impact.energy < 50) return '高雅静谧的商务氛围';
  519. if (impact.energy > 70) return '活跃动感的现代氛围';
  520. return '平衡和谐的中性氛围';
  521. }
  522. private aggregatePsychologicalEffects(data: any[]): string[] {
  523. const effects = new Set<string>();
  524. data.forEach(d => {
  525. if (d.energy > 70) effects.add('提升活力和注意力');
  526. if (d.warmth > 70) effects.add('营造温暖亲切感');
  527. if (d.sophistication > 80) effects.add('增强空间品质感');
  528. if (d.comfort > 80) effects.add('促进放松和舒适感');
  529. });
  530. return Array.from(effects);
  531. }
  532. private determineSuitableSpaces(impact: {energy: number, warmth: number, sophistication: number, comfort: number}): string[] {
  533. const spaces:any = [];
  534. if (impact.warmth > 70 && impact.comfort > 70) {
  535. spaces.push('客厅', '卧室', '餐厅');
  536. }
  537. if (impact.sophistication > 80) {
  538. spaces.push('办公室', '会议室', '接待区');
  539. }
  540. if (impact.energy > 70) {
  541. spaces.push('工作区', '健身房', '娱乐区');
  542. }
  543. if (impact.comfort > 80 && impact.energy < 50) {
  544. spaces.push('休息区', '阅读角', '冥想室');
  545. }
  546. return spaces.length > 0 ? spaces : ['通用空间'];
  547. }
  548. /**
  549. * 更新分析进度
  550. */
  551. private updateProgress(stage: AnalysisProgress['stage'], message: string, progress: number): void {
  552. this.analysisProgress$.next({ stage, message, progress });
  553. }
  554. /**
  555. * 解析分析结果
  556. */
  557. private parseAnalysisResult(data: any): ColorAnalysisResult {
  558. return {
  559. colors: data.colors || [],
  560. originalImage: data.originalImage || '',
  561. mosaicImage: data.mosaicImage || '',
  562. reportPath: data.reportPath || ''
  563. };
  564. }
  565. /**
  566. * 模拟color-get分析过程(用于开发测试)
  567. */
  568. simulateAnalysis(imageFile: File): Observable<ColorAnalysisResult> {
  569. return new Observable(observer => {
  570. this.updateProgress('processing', '开始分析...', 10);
  571. setTimeout(() => {
  572. this.updateProgress('extracting', '提取颜色信息...', 30);
  573. setTimeout(() => {
  574. this.updateProgress('generating', '生成分析报告...', 70);
  575. // 使用forkJoin来并行处理所有分析服务,添加错误处理
  576. const analysisObservables = {
  577. formAnalysis: this.formAnalysisService.analyzeImageForm(imageFile).pipe(
  578. catchError(error => {
  579. console.warn('形体分析失败,使用默认值:', error);
  580. return of(null);
  581. })
  582. ),
  583. textureAnalysis: this.textureAnalysisService.analyzeImageTexture(imageFile).pipe(
  584. catchError(error => {
  585. console.warn('质感分析失败,使用默认值:', error);
  586. return of(null);
  587. })
  588. ),
  589. patternAnalysis: this.patternAnalysisService.analyzeImagePattern(imageFile).pipe(
  590. catchError(error => {
  591. console.warn('纹理分析失败,使用默认值:', error);
  592. return of(null);
  593. })
  594. ),
  595. lightingAnalysis: this.lightingAnalysisService.analyzeImageLighting(imageFile).pipe(
  596. catchError(error => {
  597. console.warn('灯光分析失败,使用默认值:', error);
  598. return of(null);
  599. })
  600. )
  601. };
  602. forkJoin(analysisObservables).subscribe({
  603. next: (analysisResults) => {
  604. const mockResult: ColorAnalysisResult = {
  605. colors: [
  606. { hex: '#FF6B6B', rgb: { r: 255, g: 107, b: 107 }, percentage: 25.5 },
  607. { hex: '#4ECDC4', rgb: { r: 78, g: 205, b: 196 }, percentage: 18.3 },
  608. { hex: '#45B7D1', rgb: { r: 69, g: 183, b: 209 }, percentage: 15.7 },
  609. { hex: '#96CEB4', rgb: { r: 150, g: 206, b: 180 }, percentage: 12.1 },
  610. { hex: '#FFEAA7', rgb: { r: 255, g: 234, b: 167 }, percentage: 10.8 },
  611. { hex: '#DDA0DD', rgb: { r: 221, g: 160, b: 221 }, percentage: 8.9 },
  612. { hex: '#98D8C8', rgb: { r: 152, g: 216, b: 200 }, percentage: 8.7 }
  613. ],
  614. originalImage: URL.createObjectURL(imageFile),
  615. mosaicImage: URL.createObjectURL(imageFile), // 在实际应用中这里应该是处理后的图片
  616. reportPath: '/mock-report.html',
  617. // 添加增强色彩分析
  618. enhancedAnalysis: this.performEnhancedColorAnalysis([
  619. { hex: '#FF6B6B', rgb: { r: 255, g: 107, b: 107 }, percentage: 25.5 },
  620. { hex: '#4ECDC4', rgb: { r: 78, g: 205, b: 196 }, percentage: 18.3 },
  621. { hex: '#45B7D1', rgb: { r: 69, g: 183, b: 209 }, percentage: 15.7 },
  622. { hex: '#96CEB4', rgb: { r: 150, g: 206, b: 180 }, percentage: 12.1 },
  623. { hex: '#FFEAA7', rgb: { r: 255, g: 234, b: 167 }, percentage: 10.8 }
  624. ]),
  625. // 添加其他分析结果,如果分析失败则使用默认值
  626. formAnalysis: analysisResults.formAnalysis || this.getDefaultFormAnalysis(),
  627. textureAnalysis: analysisResults.textureAnalysis || this.getDefaultTextureAnalysis(),
  628. patternAnalysis: analysisResults.patternAnalysis || this.getDefaultPatternAnalysis(),
  629. lightingAnalysis: analysisResults.lightingAnalysis || this.getDefaultLightingAnalysis()
  630. };
  631. this.updateProgress('completed', '分析完成', 100);
  632. observer.next(mockResult);
  633. observer.complete();
  634. },
  635. error: (error) => {
  636. console.error('分析服务出错:', error);
  637. // 即使分析服务出错,也返回基本的颜色分析结果
  638. const fallbackResult: ColorAnalysisResult = {
  639. colors: [
  640. { hex: '#FF6B6B', rgb: { r: 255, g: 107, b: 107 }, percentage: 25.5, name: '珊瑚红' },
  641. { hex: '#4ECDC4', rgb: { r: 78, g: 205, b: 196 }, percentage: 18.3, name: '青绿色' },
  642. { hex: '#45B7D1', rgb: { r: 69, g: 183, b: 209 }, percentage: 15.7, name: '天蓝色' }
  643. ],
  644. originalImage: URL.createObjectURL(imageFile),
  645. mosaicImage: URL.createObjectURL(imageFile),
  646. reportPath: '/mock-report.html',
  647. enhancedAnalysis: this.performEnhancedColorAnalysis([
  648. { hex: '#FF6B6B', rgb: { r: 255, g: 107, b: 107 }, percentage: 25.5 },
  649. { hex: '#4ECDC4', rgb: { r: 78, g: 205, b: 196 }, percentage: 18.3 },
  650. { hex: '#45B7D1', rgb: { r: 69, g: 183, b: 209 }, percentage: 15.7 }
  651. ]),
  652. formAnalysis: this.getDefaultFormAnalysis(),
  653. textureAnalysis: this.getDefaultTextureAnalysis(),
  654. patternAnalysis: this.getDefaultPatternAnalysis(),
  655. lightingAnalysis: this.getDefaultLightingAnalysis()
  656. };
  657. this.updateProgress('completed', '分析完成(部分功能降级)', 100);
  658. observer.next(fallbackResult);
  659. observer.complete();
  660. }
  661. });
  662. }, 500);
  663. }, 300);
  664. });
  665. }
  666. /**
  667. * 获取默认形体分析结果
  668. */
  669. private getDefaultFormAnalysis(): any {
  670. return {
  671. spaceAnalysis: {
  672. spaceType: '现代简约'
  673. },
  674. lineAnalysis: {
  675. dominantLineType: 'straight',
  676. lineDirection: { horizontal: 60, vertical: 30, diagonal: 10 }
  677. }
  678. };
  679. }
  680. /**
  681. * 获取默认质感分析结果
  682. */
  683. private getDefaultTextureAnalysis(): any {
  684. return {
  685. materialClassification: {
  686. primary: '光滑表面'
  687. },
  688. surfaceProperties: {
  689. roughness: { level: 'smooth', value: 30 },
  690. glossiness: { level: 'satin', value: 50 }
  691. }
  692. };
  693. }
  694. /**
  695. * 获取默认纹理分析结果
  696. */
  697. private getDefaultPatternAnalysis(): any {
  698. return {
  699. patternRecognition: {
  700. primaryPatterns: [
  701. { type: 'geometric', confidence: 70, coverage: 40 }
  702. ],
  703. patternComplexity: { level: 'moderate', score: 50 }
  704. }
  705. };
  706. }
  707. /**
  708. * 获取默认灯光分析结果
  709. */
  710. private getDefaultLightingAnalysis(): any {
  711. return {
  712. ambientAnalysis: {
  713. lightingMood: '温馨舒适'
  714. },
  715. illuminationAnalysis: {
  716. brightness: { overall: 70 },
  717. colorTemperature: { kelvin: 3000, warmth: 'warm' }
  718. }
  719. };
  720. }
  721. }